From 71c730dae5cc15ae2107a3aded68259d1ca829fb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:46:06 +0300 Subject: [PATCH 1/3] Drop support for EOL Python 3.10 --- .github/workflows/test.yml | 1 - pyproject.toml | 3 +-- tests/test_i18n.py | 2 +- tests/test_time.py | 5 ++--- tox.ini | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76259c03..6bfd9a18 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,6 @@ jobs: - "3.13" - "3.12" - "3.11" - - "3.10" os: [windows-latest, macos-latest, ubuntu-latest] steps: diff --git a/pyproject.toml b/pyproject.toml index e125c579..eec12f37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,14 +16,13 @@ license = "MIT" license-files = [ "LICENCE" ] maintainers = [ { name = "Hugo van Kemenade" } ] authors = [ { name = "Jason Moiron", email = "jmoiron@jmoiron.net" } ] -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 20db617b..683fe3ce 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -17,7 +17,7 @@ LOCALE_DIR = Path(humanize.i18n.__file__).parent / "locale" with freeze_time("2020-02-02"): - NOW = dt.datetime.now(tz=dt.timezone.utc) + NOW = dt.datetime.now(tz=dt.UTC) @pytest.mark.parametrize("locale, one", [("de_DE", "eins"), ("fr_FR", "un")]) diff --git a/tests/test_time.py b/tests/test_time.py index f63b5cb0..887a3fc5 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -27,7 +27,7 @@ with freeze_time(FROZEN_DATE): NOW = dt.datetime.now() - NOW_UTC = dt.datetime.now(tz=dt.timezone.utc) + NOW_UTC = dt.datetime.now(tz=dt.UTC) NOW_UTC_PLUS_01_00 = dt.datetime.now(tz=dt.timezone(offset=dt.timedelta(hours=1))) TODAY = dt.date.today() TOMORROW = TODAY + ONE_DAY_DELTA @@ -327,12 +327,11 @@ def test_naturaldate(test_input: dt.date, expected: str) -> None: @freeze_time("2023-10-15 23:00:00+00:00") def test_naturaldate_tz_aware() -> None: """naturaldate should compare dates in the timezone of the given value.""" - utc = dt.timezone.utc aedt = dt.timezone(dt.timedelta(hours=11)) cest = dt.timezone(dt.timedelta(hours=2)) edt = dt.timezone(dt.timedelta(hours=-4)) pdt = dt.timezone(dt.timedelta(hours=-7)) - future = dt.datetime(2023, 10, 16, hour=6, tzinfo=utc) + future = dt.datetime(2023, 10, 16, hour=6, tzinfo=dt.UTC) # UTC: now is Oct 15, future is Oct 16 => tomorrow assert humanize.naturaldate(future) == "tomorrow" diff --git a/tox.ini b/tox.ini index 1e50273c..9197a76d 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ env_list = lint mypy py{py3} - py{310-315} + py{311-315} py{314-315}t [testenv] From 4d13dd5568ebc08e60ba91c5976f0b6c1ebd0142 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:47:24 +0300 Subject: [PATCH 2/3] Test PyPy3.12 --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bfd9a18..515f29fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ jobs: fail-fast: false matrix: python-version: + - "pypy3.12" - "pypy3.11" - "3.15t" - "3.15" @@ -69,9 +70,12 @@ jobs: name: ${{ matrix.os }} Python ${{ matrix.python-version }} success: + if: always() needs: test runs-on: ubuntu-latest name: Test successful steps: - name: Success - run: echo Test successful + env: + TEST_RESULT: ${{ needs.test.result }} + run: test "$TEST_RESULT" = "success" From 8bf85bbcbf3a9086639fdfa3faa0f269e99a63d2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:59:17 +0300 Subject: [PATCH 3/3] Update config --- .github/release-drafter.yml | 55 +++++++--------- .gitignore | 128 +----------------------------------- .pre-commit-config.yaml | 10 +-- pyproject.toml | 8 ++- src/humanize/time.py | 8 +-- tests/test_filesize.py | 2 - 6 files changed, 43 insertions(+), 168 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index ba262205..92d59256 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -2,25 +2,37 @@ name-template: "$RESOLVED_VERSION" tag-template: "$RESOLVED_VERSION" categories: + - type: "pre-exclude" + when: + label: "changelog: skip" - title: "Added" - labels: - - "changelog: Added" - - "enhancement" + semver-increment: "minor" + when: + labels: + - "changelog: Added" + - "enhancement" - title: "Changed" - label: "changelog: Changed" + semver-increment: "minor" + when: + label: "changelog: Changed" - title: "Deprecated" - label: "changelog: Deprecated" + semver-increment: "minor" + when: + label: "changelog: Deprecated" - title: "Removed" - label: "changelog: Removed" + semver-increment: "major" + when: + label: "changelog: Removed" - title: "Fixed" - labels: - - "changelog: Fixed" - - "bug" + semver-increment: "patch" + when: + labels: + - "changelog: Fixed" + - "bug" - title: "Security" - label: "changelog: Security" - -exclude-labels: - - "changelog: skip" + semver-increment: "minor" + when: + label: "changelog: Security" autolabeler: - label: "changelog: skip" @@ -29,20 +41,3 @@ autolabeler: template: | $CHANGES - -version-resolver: - major: - labels: - - "changelog: Removed" - minor: - labels: - - "changelog: Added" - - "changelog: Changed" - - "changelog: Deprecated" - - "enhancement" - - patch: - labels: - - "changelog: Fixed" - - "bug" - default: minor diff --git a/.gitignore b/.gitignore index 7ab546ca..e6f08499 100644 --- a/.gitignore +++ b/.gitignore @@ -1,158 +1,36 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -*$py.class - -# C extensions -*.so # Distribution / packaging -.Python build/ -develop-eggs/ dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ *.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ .coverage .coverage.* -.cache -nosetests.xml coverage.xml -*.cover -*.py,cover .hypothesis/ -.pytest_cache/ -cover/ # Translations *.mo -*.pot -# Django stuff: +# Logs *.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py # Environments .env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject # mkdocs documentation /site +node_modules/ # mypy -.mypy_cache/ .dmypy.json dmypy.json -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# Jetbrains IDEs +# PyCharm .idea/ # hatch-vcs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d8ee9721..dcfdfaba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ priorities: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.16.5 + rev: v0.16.10 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] @@ -47,7 +47,7 @@ repos: priority: fix-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.38.0 + rev: "0.38.2" hooks: - id: check-github-workflows priority: check @@ -61,13 +61,13 @@ repos: priority: check - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.29.0 + rev: v1.30.1 hooks: - id: zizmor priority: check - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.28.2 + rev: v2.30.0 hooks: - id: pyproject-fmt priority: fix @@ -91,7 +91,7 @@ repos: priority: fix - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.9.6 + rev: v3.9.9 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] diff --git a/pyproject.toml b/pyproject.toml index eec12f37..63817fec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ build.hooks.vcs.version-file = "src/humanize/_version.py" [tool.ruff] fix = true -lint.select = [ +lint.extend-select = [ "C4", # flake8-comprehensions "D", # pydocstyle "E", # pycodestyle errors @@ -88,11 +88,13 @@ lint.ignore = [ ] lint.per-file-ignores."tests/*" = [ "D", + "DTZ", # naive local datetimes are intentional ] lint.flake8-import-conventions.aliases.datetime = "dt" lint.flake8-import-conventions.banned-from = [ "datetime" ] lint.flake8-pytest-style.parametrize-names-type = "csv" -lint.flake8-tidy-imports.banned-api."typing.TYPE_CHECKING".msg = "Use TYPE_CHECKING = False instead" +lint.flake8-tidy-imports.banned-api."re.match".msg = "use re.fullmatch() or re.search() instead" +lint.flake8-tidy-imports.banned-api."typing.TYPE_CHECKING".msg = "use TYPE_CHECKING = False instead" lint.isort.known-first-party = [ "humanize" ] lint.isort.required-imports = [ "from __future__ import annotations" ] lint.pydocstyle.convention = "google" @@ -114,6 +116,8 @@ minversion = "9.0" testpaths = [ "tests" ] [tool.coverage] +run.disable_warnings = [ "no-sysmon" ] # 3.11 +run.core = "sysmon" # default for 3.14+, available for 3.12+ # Regexes for lines to exclude from consideration report.exclude_also = [ # Don't complain if non-runnable code isn't run: diff --git a/src/humanize/time.py b/src/humanize/time.py index 82a80424..7c4a28eb 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -49,7 +49,7 @@ def __lt__(self, other: Any) -> Any: def _now() -> dt.datetime: import datetime as dt - return dt.datetime.now() + return dt.datetime.now() # noqa: DTZ005 # naive local time is intentional def _abs_timedelta(delta: dt.timedelta) -> dt.timedelta: @@ -321,7 +321,7 @@ def _convert_aware_datetime( import datetime as dt if isinstance(value, dt.datetime) and value.tzinfo is not None: - value = dt.datetime.fromtimestamp(value.timestamp()) + value = dt.datetime.fromtimestamp(value.timestamp()) # noqa: DTZ006 return value @@ -341,7 +341,7 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str: if isinstance(value, dt.datetime) and value.tzinfo is not None: today = dt.datetime.now(value.tzinfo).date() else: - today = dt.date.today() + today = dt.date.today() # noqa: DTZ011 value = dt.date(value.year, value.month, value.day) except AttributeError: # Passed value wasn't date-ish @@ -372,7 +372,7 @@ def naturaldate(value: dt.date | dt.datetime) -> str: if isinstance(value, dt.datetime) and value.tzinfo is not None: today = dt.datetime.now(value.tzinfo).date() else: - today = dt.date.today() + today = dt.date.today() # noqa: DTZ011 value = dt.date(value.year, value.month, value.day) except AttributeError: # Passed value wasn't date-ish diff --git a/tests/test_filesize.py b/tests/test_filesize.py index e6956399..645751b9 100644 --- a/tests/test_filesize.py +++ b/tests/test_filesize.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """Tests for filesize humanizing.""" from __future__ import annotations