diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 1954918..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,21 +0,0 @@ -# Contributing - -## Linting - -Linting is run on the CI using [prek](https://prek.j178.dev//), and can be run locally: - -```sh -pip install prek -prek install # optional: to run when you commit, on just the staged changes -prek run --all-files # to run on all files now -``` - -## Docstrings - -Follow -[Google style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) -for docstrings. - -## Localization - -See [README](https://github.com/python-humanize/humanize#localization). diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index aa4bcd7..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: hugovk -tidelift: "pypi/humanize" diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2396030..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ -### What did you do? - -### What did you expect to happen? - -### What actually happened? - -### What versions are you using? - -* OS: -* Python: -* Humanize: - -Please include **code** that reproduces the issue. - -The [best reproductions](https://stackoverflow.com/help/minimal-reproducible-example) -are -[self-contained scripts](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) -with minimal dependencies. - -```python -code goes here -``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 7fa46ec..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,7 +0,0 @@ -Fixes # - -Changes proposed in this pull request: - -* -* -* diff --git a/.github/SECURITY.md b/.github/SECURITY.md deleted file mode 100644 index 74487a6..0000000 --- a/.github/SECURITY.md +++ /dev/null @@ -1,4 +0,0 @@ -# Security policy - -Security reports can be made via [Tidelift](https://tidelift.com/security). Tidelift -will coordinate the fix and disclosure. diff --git a/.github/labels.yml b/.github/labels.yml deleted file mode 100644 index 7534b02..0000000 --- a/.github/labels.yml +++ /dev/null @@ -1,74 +0,0 @@ -# Default GitHub labels -- color: d73a4a - description: "Something isn't working" - name: bug -- color: cfd3d7 - description: "This issue or pull request already exists" - name: duplicate -- color: a2eeef - description: "New feature or request" - name: enhancement -- color: 7057ff - description: "Good for newcomers" - name: good first issue -- color: 008672 - description: "Extra attention is needed" - name: help wanted -- color: e4e669 - description: "This doesn't seem right" - name: invalid -- color: d876e3 - description: "Further information is requested" - name: question -- color: ffffff - description: "This will not be worked on" - name: wontfix - -# Keep a Changelog labels -# https://keepachangelog.com/en/1.0.0/ -- color: 0e8a16 - description: "For new features" - name: "changelog: Added" -- color: af99e5 - description: "For changes in existing functionality" - name: "changelog: Changed" -- color: FFA500 - description: "For soon-to-be removed features" - name: "changelog: Deprecated" -- color: 00A800 - description: "For any bug fixes" - name: "changelog: Fixed" -- color: ff0000 - description: "For now removed features" - name: "changelog: Removed" -- color: 045aa0 - description: "In case of vulnerabilities" - name: "changelog: Security" -- color: fbca04 - description: "Exclude PR from release draft" - name: "changelog: skip" - -# Other labels -- color: 0366d6 - description: "For dependencies" - name: dependencies -- color: 0075ca - description: "Improvements or additions to documentation" - name: documentation -- color: d0c1ff - description: "Translations need updating" - name: "needs localisation" -- color: eb6123 - description: "" - name: Hacktoberfest -- color: eb6123 - description: "To credit accepted Hacktoberfest PRs" - name: hacktoberfest-accepted -- color: e29673 - name: "needs tests" -- color: d65e88 - description: "Deploy and release" - name: release -- color: fbca04 - description: "Unit tests, linting, CI, etc." - name: testing diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index ba26220..0000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,48 +0,0 @@ -name-template: "$RESOLVED_VERSION" -tag-template: "$RESOLVED_VERSION" - -categories: - - title: "Added" - labels: - - "changelog: Added" - - "enhancement" - - title: "Changed" - label: "changelog: Changed" - - title: "Deprecated" - label: "changelog: Deprecated" - - title: "Removed" - label: "changelog: Removed" - - title: "Fixed" - labels: - - "changelog: Fixed" - - "bug" - - title: "Security" - label: "changelog: Security" - -exclude-labels: - - "changelog: skip" - -autolabeler: - - label: "changelog: skip" - branch: - - "/pre-commit-ci-update-config/" - -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/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 14778ed..0000000 --- a/.github/renovate.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended", ":semanticCommitsDisabled"], - "labels": ["changelog: skip", "dependencies"], - "minimumReleaseAge": "7 days", - "packageRules": [ - { - "groupName": "github-actions", - "matchManagers": ["github-actions"], - "separateMajorMinor": false - }, - { - "groupName": "docs/requirements.txt", - "matchFileNames": ["docs/requirements.txt"], - "separateMajorMinor": false - } - ], - "schedule": ["on the first day of the month"] -} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index face743..0000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Benchmark - -on: [push, pull_request, workflow_dispatch] - -permissions: {} - -env: - FORCE_COLOR: 1 - PIP_DISABLE_PIP_VERSION_CHECK: 1 - -jobs: - benchmarks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - - name: Set up Python 3.14 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: "3.14" - allow-prereleases: true - - - name: Install gettext - run: | - sudo apt install gettext - - - name: Generate translation binaries - run: | - scripts/generate-translation-binaries.sh - - - name: Install dependencies - run: pip install -e ".[tests]" - - - name: Run benchmarks - uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1 - with: - mode: simulation - run: pytest tests/test_benchmarks.py --codspeed diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 60b86e1..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Docs - -on: [push, pull_request, workflow_dispatch] - -permissions: {} - -env: - FORCE_COLOR: 1 - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: "3.x" - - - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - - - name: Docs - run: | - uvx --with tox-uv tox -e docs diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml deleted file mode 100644 index f81156d..0000000 --- a/.github/workflows/labels.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Sync labels - -on: - push: - branches: - - main - paths: - - .github/labels.yml - workflow_dispatch: - -jobs: - sync: - permissions: - pull-requests: write - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # v1.3.0 - with: - prune: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 36aed10..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Lint - -on: [push, pull_request, workflow_dispatch] - -permissions: {} - -env: - FORCE_COLOR: 1 - RUFF_OUTPUT_FORMAT: github - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4 - - mypy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: "3.x" - - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - - name: Mypy - run: uvx --with tox-uv tox -e mypy diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 4929270..0000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Release drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - main - # pull_request event is required only for autolabeler - pull_request: - # Only following types are handled by the action, but one can default to all as well - types: [opened, reopened, synchronize] - # pull_request_target event is required for autolabeler to support PRs from forks - # pull_request_target: - # types: [opened, reopened, synchronize] - workflow_dispatch: - -jobs: - update_release_draft: - if: | - github.event.repository.fork == false - && github.event_name != 'pull_request' - permissions: - # write permission is required to create a GitHub Release - contents: write - runs-on: ubuntu-slim - steps: - # Drafts your next release notes as pull requests are merged into "main" - - uses: release-drafter/release-drafter@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0 - - autolabeler: - if: | - github.event.repository.fork == false - && github.event_name == 'pull_request' - permissions: - # write permission is required for autolabeler - pull-requests: write - runs-on: ubuntu-slim - steps: - - uses: release-drafter/release-drafter/autolabeler@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4d7a4e5..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Release - -on: - push: - branches: [main] - tags: ["*"] - pull_request: - branches: [main] - release: - types: - - published - workflow_dispatch: - -permissions: {} - -env: - FORCE_COLOR: 1 - -jobs: - # Always build & lint package. - build-package: - name: Build & verify package - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Install gettext - run: | - sudo apt install gettext - - - name: Generate translation binaries - run: | - scripts/generate-translation-binaries.sh - - - uses: hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0 - - # Upload to Test PyPI on every commit on main. - release-test-pypi: - name: Publish in-dev package to test.pypi.org - if: | - github.event.repository.fork == false - && github.event_name == 'push' - && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - needs: build-package - - permissions: - id-token: write - - steps: - - name: Download packages built by build-and-inspect-python-package - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: Packages - path: dist - - - name: Upload package to Test PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 - with: - repository-url: https://test.pypi.org/legacy/ - - # Upload to real PyPI on GitHub Releases. - release-pypi: - name: Publish released package to pypi.org - if: | - github.event.repository.fork == false - && github.event.action == 'published' - runs-on: ubuntu-latest - needs: build-package - - permissions: - id-token: write - - steps: - - name: Download packages built by build-and-inspect-python-package - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: Packages - path: dist - - - name: Upload package to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/require-pr-label.yml b/.github/workflows/require-pr-label.yml deleted file mode 100644 index 47bf92b..0000000 --- a/.github/workflows/require-pr-label.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Require PR label - -on: - pull_request: - types: [opened, reopened, labeled, unlabeled, synchronize] - -jobs: - label: - runs-on: ubuntu-slim - - permissions: - issues: write - pull-requests: write - - steps: - - uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5.2 - with: - mode: minimum - count: 1 - labels: | - changelog: Added - changelog: Changed - changelog: Deprecated - changelog: Fixed - changelog: Removed - changelog: Security - changelog: skip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 6cb6e61..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Test - -on: [push, pull_request, workflow_dispatch] - -permissions: {} - -env: - FORCE_COLOR: 1 - PIP_DISABLE_PIP_VERSION_CHECK: 1 - -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: - - "pypy3.11" - - "3.15t" - - "3.15" - - "3.14t" - - "3.14" - - "3.13" - - "3.12" - - "3.11" - - "3.10" - os: [windows-latest, macos-latest, ubuntu-latest] - - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - - name: Set PYTHON_GIL - if: endsWith(matrix.python-version, 't') - run: | - echo "PYTHON_GIL=0" >> "$GITHUB_ENV" - - - name: Install Linux dependencies - if: startsWith(matrix.os, 'ubuntu') - run: | - sudo apt install gettext - - - name: Install macOS dependencies - if: startsWith(matrix.os, 'macos') - run: | - brew install gettext - - - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - - - name: Generate translation binaries - run: | - scripts/generate-translation-binaries.sh - - - name: Tox tests - run: | - uvx --python ${{ matrix.python-version }} --with tox-uv tox -e py - - - name: Upload coverage - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - flags: ${{ matrix.os }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} - - success: - needs: test - runs-on: ubuntu-latest - name: Test successful - steps: - - name: Success - run: echo Test successful diff --git a/src/humanize/filesize.py b/src/humanize/filesize.py index fb675fd..bdd395a 100644 --- a/src/humanize/filesize.py +++ b/src/humanize/filesize.py @@ -4,6 +4,7 @@ __lazy_modules__ = {"humanize.i18n", "math"} +import math from math import log from humanize.i18n import _gettext as _ @@ -67,6 +68,16 @@ def naturalsize( '30000.0 QB' >>> naturalsize(-4096, True) '-4.0 KiB' + >>> naturalsize(0) + '0 Bytes' + >>> naturalsize(float('nan')) + Traceback (most recent call last): + ... + ValueError: naturalsize() requires a finite number of bytes, got nan + >>> naturalsize(float('inf')) + Traceback (most recent call last): + ... + ValueError: naturalsize() requires a finite number of bytes, got inf ``` @@ -90,6 +101,17 @@ def naturalsize( base = 1024 if (gnu or binary) else 1000 bytes_ = float(value) + # Reject NaN and infinities up front. A NaN would propagate into log()/abs() and + # later raise a confusing "cannot convert float NaN to integer" from int() on + # the suffix exponent; +inf/-inf would silently render as "inf QB" / "-inf QB", + # which is not a meaningful filesize for any real caller. math.isfinite() also + # rejects strings, so non-numeric values reach the float() call below and raise + # ValueError as before. + + if not math.isfinite(bytes_): + raise ValueError( + f"naturalsize() requires a finite number of bytes, got {value!r}" + ) abs_bytes = abs(bytes_) if abs_bytes == 1 and not gnu: diff --git a/tests/test_filesize.py b/tests/test_filesize.py index e695639..028acd5 100644 --- a/tests/test_filesize.py +++ b/tests/test_filesize.py @@ -103,3 +103,20 @@ def test_naturalsize(test_args: list[int] | list[int | bool], expected: str) -> test_args[0] = f"-{test_args[0]}" assert humanize.naturalsize(*test_args) == "-" + expected + + +@pytest.mark.parametrize("value", [float("nan"), float("inf"), float("-inf")]) +def test_naturalsize_rejects_non_finite(value: float) -> None: + # Non-finite inputs used to surface a confusing low-level error + # ("cannot convert float NaN to integer" from int(), or the literal string + # "inf QB" for +/-inf) rather than a clear rejection at the call site. + with pytest.raises(ValueError, match="finite number of bytes"): + humanize.naturalsize(value) + + +@pytest.mark.parametrize("value", ["nan", "inf", "-inf", "NaN", "INF"]) +def test_naturalsize_rejects_non_finite_strings(value: str) -> None: + # The same finite-check must trigger for string inputs that float() accepts + # but that don't represent a meaningful byte count. + with pytest.raises(ValueError, match="finite number of bytes"): + humanize.naturalsize(value)