diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 73b5db6c..2626bb76 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.10" - name: Get full Python version id: full-python-version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a18a8140..812d63b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,14 +21,14 @@ jobs: platform: linux - os: windows ls: dir - interpreter: 3.9 3.10 3.11 3.12 3.13 3.14 pypy3.9 pypy3.10 + interpreter: 3.10 3.11 3.12 3.13 3.14 pypy3.10 - os: windows ls: dir target: aarch64 interpreter: 3.11 3.12 3.13 3.14 - os: macos target: aarch64 - interpreter: 3.9 3.10 3.11 3.12 3.13 3.14 pypy3.9 pypy3.10 + interpreter: 3.10 3.11 3.12 3.13 3.14 pypy3.10 - os: ubuntu platform: linux target: aarch64 @@ -44,11 +44,11 @@ jobs: - os: ubuntu platform: linux target: ppc64le - interpreter: 3.9 3.10 3.11 3.12 3.13 3.14 + interpreter: 3.10 3.11 3.12 3.13 3.14 - os: ubuntu platform: linux target: s390x - interpreter: 3.9 3.10 3.11 3.12 3.13 3.14 + interpreter: 3.10 3.11 3.12 3.13 3.14 runs-on: ${{ matrix.os }}-latest steps: @@ -66,7 +66,7 @@ jobs: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux || 'auto' }} container: ${{ matrix.container }} - args: --release --out dist --interpreter ${{ matrix.interpreter || '3.9 3.10 3.11 3.12 3.13 pypy3.9 pypy3.10' }} ${{ matrix.extra-build-args }} + args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12 3.13 pypy3.10' }} ${{ matrix.extra-build-args }} rust-toolchain: stable docker-options: -e CI diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1fbce01f..290960d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [Ubuntu, MacOS, Windows] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] defaults: run: shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98ac5c45..f611b1f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: debug-statements - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + rev: v0.14.11 hooks: - id: ruff - id: ruff-format diff --git a/CHANGELOG.md b/CHANGELOG.md index 81183e4a..a3fe4dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Change Log +- Dropped support for Python 3.9 [#930](https://github.com/python-pendulum/pendulum/pull/930) + ## [3.1.0] - 2025-04-19 ### Added diff --git a/README.rst b/README.rst index 63422f14..e2ae92f2 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ Pendulum Python datetimes made easy. -Supports Python **3.9 and newer**. +Supports Python **3.10 and newer**. .. code-block:: python diff --git a/poetry.lock b/poetry.lock index 8411da19..42ef7517 100644 --- a/poetry.lock +++ b/poetry.lock @@ -208,7 +208,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["benchmark", "test"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -267,31 +267,6 @@ files = [ [package.extras] license = ["ukkonen"] -[[package]] -name = "importlib-metadata" -version = "8.5.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["benchmark", "doc"] -markers = "python_version == \"3.9\"" -files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -334,9 +309,6 @@ files = [ {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ] -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - [package.extras] docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] @@ -525,7 +497,6 @@ files = [ click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" markdown = ">=3.3.6" markupsafe = ">=2.0.1" @@ -554,7 +525,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} mergedeep = ">=1.3.4" platformdirs = ">=2.2.0" pyyaml = ">=5.1" @@ -859,7 +829,6 @@ files = [ [package.dependencies] cffi = ">=1.17.1" -importlib-metadata = {version = ">=8.5.0", markers = "python_version < \"3.10\""} pytest = ">=3.8" rich = ">=13.8.1" @@ -1171,7 +1140,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["benchmark", "build", "dev", "test", "typing"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1258,7 +1227,7 @@ files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -markers = {benchmark = "python_version < \"3.11\"", dev = "python_version < \"3.11\""} +markers = {benchmark = "python_version == \"3.10\"", dev = "python_version == \"3.10\""} [[package]] name = "tzdata" @@ -1336,31 +1305,10 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] -[[package]] -name = "zipp" -version = "3.21.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["benchmark", "doc"] -markers = "python_version == \"3.9\"" -files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - [extras] test = ["time-machine"] [metadata] lock-version = "2.1" -python-versions = ">=3.9" -content-hash = "491ee653cad58327fa9ff27388a6d0402bc8d5202b08bca616042f5d5ae12176" +python-versions = ">=3.10" +content-hash = "f3c800d494fd36b67b21a8c8316f5bba2c20b99c457350e2c80f3103f9a08a74" diff --git a/pyproject.toml b/pyproject.toml index 265145dd..7ca15c99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,14 +3,13 @@ name = "pendulum" version = "3.2.0.dev0" description = "Python datetimes made easy" readme = "README.rst" -requires-python = ">=3.9" +requires-python = ">=3.10" license = { text = "MIT License" } authors = [{ name = "Sébastien Eustace", email = "sebastien@eustace.io" }] keywords = ['datetime', 'date', 'time'] classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -72,7 +71,7 @@ include = [ [tool.ruff] fix = true line-length = 88 -target-version = "py39" +target-version = "py310" extend-exclude = [ # External to the project's coding standards: "docs/*", diff --git a/src/pendulum/__init__.py b/src/pendulum/__init__.py index e1d02177..16ae0865 100644 --- a/src/pendulum/__init__.py +++ b/src/pendulum/__init__.py @@ -5,7 +5,6 @@ from functools import cache from typing import TYPE_CHECKING from typing import Any -from typing import Union from typing import cast from typing import overload @@ -117,7 +116,7 @@ def _safe_timezone( obj = int(offset.total_seconds()) - obj = cast("Union[str, int]", obj) + obj = cast("str | int", obj) return timezone(obj) diff --git a/src/pendulum/datetime.py b/src/pendulum/datetime.py index e6f19eb2..da89b13d 100644 --- a/src/pendulum/datetime.py +++ b/src/pendulum/datetime.py @@ -6,9 +6,7 @@ from typing import TYPE_CHECKING from typing import Any -from typing import Callable from typing import ClassVar -from typing import Optional from typing import cast from typing import overload @@ -42,7 +40,9 @@ if TYPE_CHECKING: - from typing_extensions import Literal + from collections.abc import Callable + from typing import Literal + from typing_extensions import Self from typing_extensions import SupportsIndex @@ -1006,7 +1006,7 @@ def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - dt = cast("Optional[Self]", getattr(self, f"_nth_of_{unit}")(nth, day_of_week)) + dt = cast("Self | None", getattr(self, f"_nth_of_{unit}")(nth, day_of_week)) if not dt: raise PendulumException( f"Unable to find occurrence {nth}" diff --git a/src/pendulum/formatting/formatter.py b/src/pendulum/formatting/formatter.py index 51c90128..b09977d0 100644 --- a/src/pendulum/formatting/formatter.py +++ b/src/pendulum/formatting/formatter.py @@ -6,7 +6,6 @@ from re import Match from typing import TYPE_CHECKING from typing import Any -from typing import Callable from typing import ClassVar from typing import cast @@ -16,6 +15,7 @@ if TYPE_CHECKING: + from collections.abc import Callable from collections.abc import Sequence from pendulum import Timezone diff --git a/src/pendulum/parsing/__init__.py b/src/pendulum/parsing/__init__.py index b66cf1a2..20ebed40 100644 --- a/src/pendulum/parsing/__init__.py +++ b/src/pendulum/parsing/__init__.py @@ -9,7 +9,6 @@ from datetime import datetime from datetime import time from typing import Any -from typing import Optional from typing import cast from dateutil import parser @@ -90,7 +89,7 @@ def _normalize( return parsed if isinstance(parsed, time): - now = cast("Optional[datetime]", options["now"]) or datetime.now() + now = cast("datetime | None", options["now"]) or datetime.now() return datetime( now.year, diff --git a/src/pendulum/time.py b/src/pendulum/time.py index fb3150fa..e5bf22aa 100644 --- a/src/pendulum/time.py +++ b/src/pendulum/time.py @@ -5,7 +5,6 @@ from datetime import time from datetime import timedelta from typing import TYPE_CHECKING -from typing import Optional from typing import cast from typing import overload @@ -21,7 +20,8 @@ if TYPE_CHECKING: - from typing_extensions import Literal + from typing import Literal + from typing_extensions import Self from typing_extensions import SupportsIndex @@ -284,7 +284,7 @@ def replace( minute, second, microsecond, - tzinfo=cast("Optional[datetime.tzinfo]", tzinfo), + tzinfo=cast("datetime.tzinfo | None", tzinfo), fold=fold, ) return self.__class__( diff --git a/tox.ini b/tox.ini index d45c3e2b..afff59dc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = true -envlist = py37, py38, py39, py310, pypy3 +envlist = py310, py311, py312, py313, pypy3 [testenv] whitelist_externals = poetry