pax_global_header00006660000000000000000000000064150752503000014507gustar00rootroot0000000000000052 comment=e56b78fcd4c1e79aa36c1e4700fca34c8ff540fe setuptools-scm-9.2.2/000077500000000000000000000000001507525030000145225ustar00rootroot00000000000000setuptools-scm-9.2.2/.cursor/000077500000000000000000000000001507525030000161155ustar00rootroot00000000000000setuptools-scm-9.2.2/.cursor/rules/000077500000000000000000000000001507525030000172475ustar00rootroot00000000000000setuptools-scm-9.2.2/.cursor/rules/test-running.mdc000066400000000000000000000003121507525030000223650ustar00rootroot00000000000000--- description: run tests with uv tooling globs: alwaysApply: true --- use `uv run pytest` to run tests use uv to manage dependencies follow preexisting conventions in the project - use the fixturessetuptools-scm-9.2.2/.git_archival.txt000066400000000000000000000001521507525030000177730ustar00rootroot00000000000000node: e56b78fcd4c1e79aa36c1e4700fca34c8ff540fe node-date: 2025-10-19T22:57:36+02:00 describe-name: v9.2.2 setuptools-scm-9.2.2/.gitattributes000066400000000000000000000000401507525030000174070ustar00rootroot00000000000000.git_archival.txt export-subst setuptools-scm-9.2.2/.github/000077500000000000000000000000001507525030000160625ustar00rootroot00000000000000setuptools-scm-9.2.2/.github/FUNDING.yml000066400000000000000000000000361507525030000176760ustar00rootroot00000000000000tidelift: pypi/setuptools-scm setuptools-scm-9.2.2/.github/dependabot.yml000066400000000000000000000003151507525030000207110ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" setuptools-scm-9.2.2/.github/release.yml000066400000000000000000000001141507525030000202210ustar00rootroot00000000000000changelog: exclude: authors: - dependabot - pre-commit-ci setuptools-scm-9.2.2/.github/workflows/000077500000000000000000000000001507525030000201175ustar00rootroot00000000000000setuptools-scm-9.2.2/.github/workflows/api-check.yml000066400000000000000000000051351507525030000224720ustar00rootroot00000000000000name: API Stability Check on: pull_request: push: branches: - "*" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true env: FORCE_COLOR: 1 jobs: api-check: name: Check API stability with griffe runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.11' - name: Install dependencies run: | pip install -U pip setuptools pip install -e .[test] pip install griffe - name: Run griffe API check id: griffe-check continue-on-error: true run: | echo "Running griffe API stability check..." if griffe check setuptools_scm -ssrc -f github; then echo "api_check_result=success" >> $GITHUB_OUTPUT echo "exit_code=0" >> $GITHUB_OUTPUT else exit_code=$? echo "api_check_result=warning" >> $GITHUB_OUTPUT echo "exit_code=$exit_code" >> $GITHUB_OUTPUT exit $exit_code fi - name: Report API check result if: always() uses: actions/github-script@v8 with: script: | const result = '${{ steps.griffe-check.outputs.api_check_result }}' const exitCode = '${{ steps.griffe-check.outputs.exit_code }}' if (result === 'success') { core.notice('API stability check passed - no breaking changes detected') await core.summary .addHeading('✅ API Stability Check: Passed', 2) .addRaw('No breaking changes detected in the public API') .write() } else if (result === 'warning') { core.warning(`API stability check detected breaking changes (exit code: ${exitCode}). Please review the API changes above.`) await core.summary .addHeading('⚠️ API Stability Warning', 2) .addRaw('Breaking changes detected in the public API. Please review the changes reported above.') .addRaw(`\n\nExit code: ${exitCode}`) .write() } else { core.error('API stability check failed to run properly') await core.summary .addHeading('❌ API Stability Check: Failed', 2) .addRaw('The griffe check failed to execute. This may indicate griffe is not installed or there was an error.') .write() }setuptools-scm-9.2.2/.github/workflows/python-tests.yml000066400000000000000000000126201507525030000233240ustar00rootroot00000000000000name: python tests+artifacts+release on: pull_request: push: branches: - "*" tags: - "v*" release: types: [published] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true env: FORCE_COLOR: 1 jobs: package: name: Build & inspect our package. runs-on: ubuntu-latest env: # Use no-local-version for package builds to ensure clean versions for PyPI uploads SETUPTOOLS_SCM_NO_LOCAL: "1" steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: hynek/build-and-inspect-python-package@v2 test: needs: [package] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python_version: [ '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10' ] os: [windows-latest, ubuntu-latest] #, macos-latest] include: - os: windows-latest python_version: 'msys2' env: # Enable tracemalloc to debug gc errors with popen objects (especially on Windows) PYTHONTRACEMALLOC: "1" name: ${{ matrix.os }} - Python ${{ matrix.python_version }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup python uses: actions/setup-python@v6 if: matrix.python_version != 'msys2' with: python-version: ${{ matrix.python_version }} architecture: x64 - name: Install the latest version of uv uses: astral-sh/setup-uv@v6 - name: Setup MSYS2 uses: msys2/setup-msys2@v2 if: matrix.python_version == 'msys2' with: msystem: MINGW64 install: git mingw-w64-x86_64-python mingw-w64-x86_64-python-setuptools update: true - name: Setup GnuPG and Mercurial on Windows # At present, the Windows VMs only come with the copy of GnuPG that's bundled # with Git for Windows. If we want to use this version _and_ be able to set # arbitrary GnuPG home directories, then the test would need to figure out when # to convert Windows-style paths into Unix-style paths with cygpath, which is # unreasonable. # # Instead, we'll install a version of GnuPG that can handle Windows-style paths. # However, due to , installation fails if the PATH # environment variable is too long. Consequently, we need to shorten PATH to # something minimal before we can install GnuPG. For further details, see # . # # Additionally, we'll explicitly set `gpg.program` to ensure Git for Windows # doesn't invoke the bundled GnuPG, otherwise we'll run into # . See also: . # # Windows runners no longer ship with Mercurial pre-installed, so we install # it via Chocolatey using the 'hg' package. run: | $env:PATH = "C:\Program Files\Git\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\ProgramData\Chocolatey\bin" [Environment]::SetEnvironmentVariable("Path", $env:PATH, "Machine") choco install gnupg hg -y --no-progress echo "C:\Program Files (x86)\gnupg\bin" >> $env:GITHUB_PATH echo "C:\Program Files\Mercurial\" >> $env:GITHUB_PATH git config --system gpg.program "C:\Program Files (x86)\gnupg\bin\gpg.exe" if: runner.os == 'Windows' - run: uv sync --group test --group docs --extra rich - uses: actions/download-artifact@v5 with: name: Packages path: dist - shell: bash run: uv pip install "$(echo -n dist/*whl)" - run: | $(hg debuginstall --template "{pythonexe}") -m pip install hg-git --user if: matrix.os == 'ubuntu-latest' # this hopefully helps with os caches, hg init sometimes gets 20s timeouts - run: hg version - run: uv run pytest timeout-minutes: 25 dist_upload: runs-on: ubuntu-latest if: (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) || (github.event_name == 'release' && github.event.action == 'published') permissions: id-token: write needs: [test] steps: - uses: actions/download-artifact@v5 with: name: Packages path: dist - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 upload-release-assets: runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' needs: [test] permissions: contents: write steps: - uses: actions/download-artifact@v5 with: name: Packages path: dist - name: Upload release assets uses: softprops/action-gh-release@v2 with: files: dist/* fail_on_unmatched_files: true test-pypi-upload: runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: [test] permissions: id-token: write steps: - uses: actions/download-artifact@v5 with: name: Packages path: dist - name: Publish package to PyPI continue-on-error: true uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ setuptools-scm-9.2.2/.gitignore000066400000000000000000000012271507525030000165140ustar00rootroot00000000000000### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion *.iml ## Directory-based project format: .idea/ ### Other editors .*.swp .vscode/ ### Python template # Byte-compiled / optimized __pycache__/ *.py[cod] *$py.class # Distribution / packaging .env/ env/ .venv/ venv/ build/ dist/ .eggs/ lib/ lib64/ *.egg-info/ # Installer logs pip-log.txt pip-delete-this-directory.txt pip-wheel-metadata # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache .pytest_cache .mypy_cache/ .entangled/ nosetests.xml coverage.xml *,cover .hypothesis/ # Sphinx documentation docs/_build/ .serena/cache/ setuptools-scm-9.2.2/.pre-commit-config.yaml000066400000000000000000000017031507525030000210040ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: check-yaml - id: debug-statements - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.13.3 hooks: - id: ruff-check args: [--fix, --exit-non-zero-on-fix, --show-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.18.2 hooks: - id: mypy args: [--strict] additional_dependencies: - types-setuptools - tokenize-rt==3.2.0 - pytest == 7.1 - importlib_metadata - typing-extensions>=4.5 - rich - repo: https://github.com/scientific-python/cookie rev: 2025.10.01 hooks: - id: sp-repo-review - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell args: [-w, --ignore-words-list=hist,nd,te] setuptools-scm-9.2.2/.readthedocs.yaml000066400000000000000000000003531507525030000177520ustar00rootroot00000000000000version: 2 mkdocs: configuration: mkdocs.yml build: os: ubuntu-24.04 tools: python: "3.13" jobs: install: - pip install -U pip # Official recommended way - pip install . - pip install --group docs setuptools-scm-9.2.2/.serena/000077500000000000000000000000001507525030000160555ustar00rootroot00000000000000setuptools-scm-9.2.2/.serena/memories/000077500000000000000000000000001507525030000176755ustar00rootroot00000000000000setuptools-scm-9.2.2/.serena/memories/done_checklist.md000066400000000000000000000010571507525030000232000ustar00rootroot00000000000000Before considering a task done - Code quality - Ruff clean: uv run ruff check . - Types clean: uv run mypy - Tests - All tests green: uv run pytest - New/changed behavior covered with tests (use project fixtures) - Docs - Update docs if user-facing behavior changed - Build docs cleanly: uv run mkdocs build --clean --strict - Packaging - If relevant: uv run python -m build && uv run twine check dist/* - Housekeeping - Follow existing naming and module structure; keep functions focused and typed - Update `CHANGELOG.md` when appropriate setuptools-scm-9.2.2/.serena/memories/project_overview.md000066400000000000000000000024531507525030000236170ustar00rootroot00000000000000Project: setuptools-scm Purpose - Extract and infer Python package versions from SCM metadata (Git/Mercurial) at build/runtime. - Provide setuptools integrations (dynamic version, file finders) and fallbacks for archival/PKG-INFO. Tech Stack - Language: Python (3.8–3.13) - Packaging/build: setuptools (>=61), packaging; console scripts via entry points - Tooling: uv (dependency and run), pytest, mypy (strict), ruff (lint, isort), mkdocs (docs), tox (optional/matrix), wheel/build Codebase Structure (high level) - src/setuptools_scm/: library code - _cli.py, __main__.py: CLI entry (`python -m setuptools_scm`, `setuptools-scm`) - git.py, hg.py, hg_git.py: VCS parsing - _file_finders/: discover files for sdist - _integration/: setuptools and pyproject integration - version.py and helpers: version schemes/local version logic - discover.py, fallbacks.py: inference and archival fallbacks - testing/: pytest suite and fixtures - docs/: mkdocs documentation - pyproject.toml: project metadata, pytest and ruff config - tox.ini: alternate CI/matrix, flake8 defaults - uv.lock: locked dependencies Conventions - Use uv to run commands (`uv run ...`); tests live under `testing/` per pytest config. - Type hints throughout; strict mypy enforced; ruff governs lint rules and import layout (isort in ruff). setuptools-scm-9.2.2/.serena/memories/style_and_conventions.md000066400000000000000000000015711507525030000246320ustar00rootroot00000000000000Style and Conventions - Typing - mypy strict is enabled; add precise type hints for public functions/classes. - Prefer explicit/clear types; avoid `Any` and unsafe casts. - Linting/Imports - Ruff is the canonical linter (config in pyproject). Respect its rules and isort settings (single-line imports, ordered, types grouped). - Flake8 config exists in tox.ini but ruff linting is primary. - Formatting - Follow ruff guidance; keep lines <= 88 where applicable (flake8 reference). - Testing - Pytest with `testing/` as testpath; default 5m timeout; warnings treated as errors. - Use existing fixtures; add `@pytest.mark` markers if needed (see pyproject markers). - Logging - Tests run with log level info/debug; avoid noisy logs in normal library code. - General - Small, focused functions; early returns; explicit errors. Keep APIs documented with concise docstrings. setuptools-scm-9.2.2/.serena/memories/suggested_commands.md000066400000000000000000000014041507525030000240710ustar00rootroot00000000000000Environment - Install deps (uses default groups test, docs): - uv sync Core Dev - Run tests: - uv run pytest - Lint (ruff): - uv run ruff check . - uv run ruff check . --fix # optional autofix - Type check (mypy strict): - uv run mypy - Build docs: - uv run mkdocs serve --dev-addr localhost:8000 - uv run mkdocs build --clean --strict Entrypoints / Tooling - CLI version/debug: - uv run python -m setuptools_scm --help - uv run python -m setuptools_scm - uv run setuptools-scm --help - Build dist and verify: - uv run python -m build - uv run twine check dist/* - Optional matrix via tox: - uv run tox -q Git/Linux Utilities (Linux host) - git status / git log --oneline --graph --decorate - ls -la; find . -name "pattern"; grep -R "text" . setuptools-scm-9.2.2/.serena/project.yml000066400000000000000000000106431507525030000202520ustar00rootroot00000000000000# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby) # * For C, use cpp # * For JavaScript, use typescript # Special requirements: # * csharp: Requires the presence of a .sln file in the project folder. language: python # whether to use the project's gitignore file to ignore files # Added on 2025-04-07 ignore_all_files_in_gitignore: true # list of additional paths to ignore # same syntax as gitignore, so you can use * and ** # Was previously called `ignored_dirs`, please update your config if you are using that. # Added (renamed)on 2025-04-07 ignored_paths: [] # whether the project is in read-only mode # If set to true, all editing tools will be disabled and attempts to use them will result in an error # Added on 2025-04-18 read_only: false # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. # Below is the complete list of tools for convenience. # To make sure you have the latest list of tools, and to view their descriptions, # execute `uv run scripts/print_tool_overview.py`. # # * `activate_project`: Activates a project by name. # * `check_onboarding_performed`: Checks whether project onboarding was already performed. # * `create_text_file`: Creates/overwrites a file in the project directory. # * `delete_lines`: Deletes a range of lines within a file. # * `delete_memory`: Deletes a memory from Serena's project-specific memory store. # * `execute_shell_command`: Executes a shell command. # * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. # * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). # * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file or directory. # * `initial_instructions`: Gets the initial instructions for the current project. # Should only be used in settings where the system prompt cannot be set, # e.g. in clients you have no control over, like Claude Desktop. # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. # * `insert_at_line`: Inserts content at a given line in a file. # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. # * `list_dir`: Lists files and directories in the given directory (optionally with recursion). # * `list_memories`: Lists memories in Serena's project-specific memory store. # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). # * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). # * `read_file`: Reads a file within the project directory. # * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. # * `remove_project`: Removes a project from the Serena configuration. # * `replace_lines`: Replaces a range of lines within a file with new content. # * `replace_symbol_body`: Replaces the full definition of a symbol. # * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. # * `search_for_pattern`: Performs a search for a pattern in the project. # * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. # * `switch_modes`: Activates modes by providing a list of their names # * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. # * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. # * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. # * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. excluded_tools: [] # initial prompt for the project. It will always be given to the LLM upon activating the project # (contrary to the memories, which are loaded on demand). initial_prompt: "" project_name: "setuptools_scm" setuptools-scm-9.2.2/CHANGELOG.md000066400000000000000000000704511507525030000163420ustar00rootroot00000000000000# Changelog ## v9.2.2 ### Fixed - fix #1231: don't warn about `tool.setuptools.dynamic.version` when only using file finder. The warning about combining version guessing with setuptools dynamic versions should only be issued when setuptools-scm is performing version inference, not when it's only being used for its file finder functionality. ## v9.2.1 ### Fixed - fix #1216: accept and create a warning for usages of `version = attr:` in setuptools config. unfortunately dozens of projects cargo-culted that antipattern ## v9.2.0 ### Added - add simplified activation via `setuptools-scm[simple]` extra A new streamlined way to enable version inference without requiring a `[tool.setuptools_scm]` section. When `setuptools-scm[simple]` is in `build-system.requires` and `version` is in `project.dynamic`, version inference is automatically enabled with default settings. ### Removed - unchecked simplified activation - too many projects use setups where it would fail ### Changed - refine activation logic and add unittest for the relevant cases instead of trying to speedrun setuptools ## v9.1.1 (yanked) ### Fixed - fix #1194: correctly handle version keyword when pyproject metadata is missing ## v9.1.0 (yanked) ### Fixed - complete reiteration of the decision logic for enabling version inference on setuptools_scm - shared logic for the important parts - proper deferring based in precedence of finalize options vs version keyword - unittestable for the parsing parts and the decision steps ## v9.0.3 (yanked) ### Fixed - fix #1184: verify version is dynamic if the dependency is used as indicator for enabling ## v9.0.2 (yanked) ### Fixed - fix #1184: in case setuptools-scm is a indirect dependency and no pyproject.toml section exists - don't infer the version ## v9.0.1 (yanked) ### Fixed - fix #1180: ensure version dumping works when no scm_version is given (problems in downstreams) - fix #1181: config - reintroduce control over when we expect a section to be present as it turns out there's valid use cases where setuptools_scm is not direct part of the dependencies - add codespell pre-commit hook ## v9.0.0 (yanked) ### Breaking - fix #1019: pass python version build tags from scm version to results properly ### Added - add `setuptools-scm` console_scripts entry point to make the CLI directly executable - make Mercurial command configurable by environment variable `SETUPTOOLS_SCM_HG_COMMAND` - fix #1099 use file modification times for dirty working directory timestamps instead of current time - fix #1059: add `SETUPTOOLS_SCM_PRETEND_METADATA` environment variable to override individual ScmVersion fields - add `scm` parameter support to `get_version()` function for nested SCM configuration - fix #987: expand documentation on git archival files and add cli tools for good defaults - fix #311: document github/gitlab ci pipelines that enable auto-upload to test-pypi/pypi - fix #1022: allow `version_keyword` to override `infer_version` when configuration differs - fix #554: document `fallback_root` parameter in programmatic usage and configuration ### Changed - add `pip` to test optional dependencies for improved uv venv compatibility - migrate to selectable entrypoints for better extensibility - improve typing for entry_points - refactor file modification time logic into shared helper function for better maintainability - reduce complexity of HgWorkdir.get_meta method by extracting focused helper methods - fix #1150: enable setuptools-scm when we are a build requirement - feature #1154: add the commit id the the default version file template - drop scriv - fix #921: document setuptools version requirements more consistently - 61 as minimum asn 8 as recommended minimum ### Fixed - fix #1145: ensure GitWorkdir.get_head_date returns consistent UTC dates regardless of local timezone - fix #687: ensure calendar versioning tests use consistent time context to prevent failures around midnight in non-UTC timezones - reintroduce Python 3.9 entrypoints shim for compatibility - fix #1136: update customizing.md to fix missing import - fix #1001: document the missing version schemes and add examples in the docs - fix #1115: explicitly document file finder behaviour - fix #879: add test that validates case different behavior on windows - migrate git describe command to new scm config - add support for failing on missing submodules - fix #279: expand errors when scm can be found upwards and relative_to wasn't used - fix #577: introduce explicit scmversion node and short node - fix #1100: add workaround for readthedocs worktress to the docs - fix #790: document shallow fail for rtd - fix #474: expand version not found error message to provide clearer guidance about SETUPTOOLS_SCM_PRETEND_VERSION_FOR_* environment variables - fix #324: document/recommend the v tag prefix - fix #501: add py.typed - fix #804: git - use fallback version instead of 0.0 when no version is found at all - fix #1139: use logging.lastResort instead of a own replica to avoid polluting logging._handlerList - fix #873: don't infer version in cli if --no-version is given - fix #535: accept tags from a release action in the gh ui - fix #1073: explain namespaces for release-branch-semver - fix #1052: use consistent node hash length across all SCM backends - fix #1045: reindent the `__all__` in the version template for better readability - fix #968: harden environment override finding with better normalization and typo suggestions - fix #846: add support for failing on missing submodules ## v8.3.1 ### Fixed - fixed #1131: allow self-build without importlib_metadata available on python3.9 ## v8.3.0 ### Fixed - fix #1013: use modern importlib_metadata in all cases to dedup distribution objects that must shadow based on pythonpath priority starting with python 3.10 this is part of python itself ## v8.2.1 (yanked due to legacy python issues) ### Fixed - fix #1119: also include pre/post release details in version_tuple - fix #1112: unpin setuptools for own dependencies due to ubuntu lts bugs - add python 3.13 to the support matrix ## v8.2.0 ### Added - fix #960: add a ``--force-write-version-files`` flag for the cli ### Changed - fix #950: ensure to pass encodings to io usage - fix #957: add subprocess timeout control env var - add sp-repo-review pre-commit hook ### Fixed - fix #1018: allow non-normalized versions for semver - fix #1103: respect GIT_CEILING_DIRECTORIES when trying to find git toplevels - fix #1081: add name normalized pipx entrypoint - fix #1080: clean pdm from PYTHONPATH to protect mercurial ## v8.1.0 ### Changed - inclusion of `__all__` in autogenerated `version.py` files to aid IDE autoimports ## v8.0.4 ### Changed - introduce scriv for changelog management - reconfigure local build backend to use an attribute instead of star imports from setuptools - introduce ruff as a linter - ensure the setuptools version keyword correctly load pyproject.toml configuration - add build and wheel to the test requirements for regression testing - move internal toml handling to own module ### Fixed - fix #925: allow `write_to` to be an absolute path when it's a subdirectory of the root - fix #932: ensure type annotations in version file don't cause linter issues - fix #930: temporary restore `DEFAULT_VERSION_SCHEME` and `DEFAULT_LOCAL_SCHEME` on the `setuptools-scm` package ## v8.0.3 ### Fixed - fix #918 for good - remove external importlib-metadata to avoid source only loop - fix #926: ensure mypy on python3.8 works with the version file ## v8.0.2 ### Fixed - fix #919: restore legacy version-file behaviour for external callers + add Deprecation warning - fix #918: use packaging from setuptools for self-build - fix #914: ignore the deprecated git archival plugin as its integrated now - fix #912: ensure mypy safety of the version template + regression test - fix #913: use 240s timeout instead of 20 for `git unshallow` to account for large repos or slow connections ## v8.0.1 ### Fixed - update version file template to work on older python versions by using type comments - ensure tag regex from setup.py is parsed into regex ## v8.0.0 ### breaking - remove legacy version parser api - config arg always required - turn Configuration into a dataclass - require configuration to always pass into helpers - hide file-finders implementation in private module - renamed setuptools_scm.hacks to setuptools_scm.fallbacks and drop support for pip-egg-info - remove trace function and use logging instead - unify `distance=None` and `distance=0` they should mean the same andwhere hiding dirty states that are now explicitly dirty - depend on later importlib for the full selectable api - move setuptools integration code to private sub-package - use normalized dist names for the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}` env var - drop support for python 3.7 - introduce `version_file` as replacement for `write_to` - renamed the project from `setuptools_scm` to `setuptools-scm` ### Added - created a directory for the vcs-versioning package and added it to pypi - git: expect main as possible default branch - drop version_from_scm helper - trim down exposed public api - no longer self-call twice in setuptools - add support for version schemes by import - chores - migrate own metadata to pyproject.toml - consolidate version schemes - stricter tag typing - pre-compiled regex - move helpers to private modules - support passing log levels to SETUPTOOLS_SCM_DEBUG - support using rich.logging as console log handler if installed - fix #527: type annotation in default version template - fix #549: use fallbacks when scm search raises CommandNotFoundError ### Fixed - fix #883: use HeadersParser to ensure only mime metadata in headers is used - fix #884: parse calver dates from versions with the v prefix - don't use a C locale without UTF-8 support, when running commands. ## v7.1.0 - \#748: use `tomllib` from stdlib - fix #762: handle non-ascii in setup.cfg - \#752: implement fallback file finders for archives - \#765: removed coding header in python template - declared Python 3.11 support - fix #759: update .git_archival.txt templates match git-describe invocation - fix #772: fix handling of .git-archival.txt from tagged commit ## v7.0.5 - fixes #742, #745: correctly handle accidentally released archival files ## v7.0.4 - fix #727: correctly handle incomplete archival from setuptools_scm_git_archival - fix #691: correctly handle specifying root in pyproject.toml - correct root override check condition (to ensure absolute path matching) - allow root by the cli to be considered relative to the cli (using abspath) ## v7.0.3 - fix mercurial usage when pip primes a isolated environment - fix regression for branch names on git + add a test ## v7.0.2 - fix #723 and #722: remove bootstrap dependencies - ensure we read the distribution name from `setup.cfg` if needed even for `pyproject.toml` ## v7.0.1 - fix #718: Avoid `ModuleNotFoundError` by requiring `importlib_metadata` in `python<3.8` ## v7.0.0 - drop python 3.6 support - include git archival support - fix #707: support git version detection even when git protects against mismatched owners (common with misconfigured containers, thanks @chrisburr ) - fix #548: correctly handle parsing the commit timestamp of HEAD when `log.showSignature` is set ## v6.4.2 - fix #671: `NoReturn` is not available in painfully dead python 3.6 ## v6.4.1 - fix regression #669: restore get_version signature - fix #668: harden the self-test for distribution extras ## 6.4.0 - compatibility adjustments for setuptools \>58 - only put minimal setuptools version into toml extra to warn people with old strict pins - correctly handle hg-git self-use - better mercurial detection - modernize packaging setup - python 3.10 support - better handling of setuptools install command deprecation - consider `pyproject.tomls` when running as command - use list in git describe command to avoid shell expansions while supporting both windows and posix - add `--strip-dev` flag to `python -m setuptools_scm` to print the next guessed version cleanly - ensure no-guess-dev will fail on bad tags instead of generating invalid versions - ensure we use utc everywhere to avoid confusion ## v6.3.2 - fix #629: correctly convert Version data in tags_to_version parser to avoid errors ## v6.3.1 - fix #625: restore tomli in install_requires after the regression changes in took it out and some users never added it even tho they have pyproject.toml files ## v6.3.0 ### warning This release explicitly warns on unsupported setuptools. This unfortunately has to happen as the legacy `setup_requires` mechanism incorrectly configures the setuptools working-set when a more recent setuptools version than available is required. As all releases of setuptools are affected as the historic mechanism for ensuring a working setuptools setup was shipping a `ez_setup` file next to `setup.py`, which would install the required version of setuptools. This mechanism has long since been deprecated and removed as most people haven\'t been using it ### Fixed - fix #612: depend on packaging to ensure version parsing parts - fix #611: correct the typo that hid away the toml extra and add it in `setup.py` as well - fix #615: restore support for the git_archive plugin which doesn't pass over the config - restore the ability to run on old setuptools while to avoid breaking pipelines ## v6.2.0 - fix #608: resolve tomli dependency issue by making it a hard dependency as all intended/supported install options use pip/wheel this is only a feature release - ensure python 3.10 works ## v6.1.1 - fix #605: completely disallow bdist_egg - modern enough setuptools\>=45 uses pip - fix #606: re-integrate and harden toml parsing - fix #597: harden and expand support for figuring the current distribution name from [pyproject.toml]{.title-ref} ([project.name]{.title-ref} or [tool.setuptools_scm.dist_name]{.title-ref}) section or [setup.cfg]{.title-ref} ([metadata.name]{.title-ref}) ## v6.1.0 - fix #587: don\'t fail file finders when distribution is not given - fix #524: new parameters `normalize` and `version_cls` to customize the version normalization class. - fix #585: switch from toml to tomli for toml 1.0 support - fix #591: allow to opt in for searching parent directories in the api - fix #589: handle yaml encoding using the expected defaults - fix #575: recommend storing the version_module inside of `mypkg/_version.py` - fix #571: accept branches starting with `v` as release branches - fix #557: Use `packaging.version` for `version_tuple` - fix #544: enhance errors on unsupported python/setuptools versions ## v6.0.1 - fix #537: drop node_date on old git to avoid errors on missing %cI ## v6.0.0 - fix #517: drop dead python support \>3.6 required - drop dead setuptools support \> 45 required (can install wheels) - drop egg building (use wheels) - add git node_date metadata to get the commit time-stamp of HEAD - allow version schemes to be priority ordered lists of version schemes - support for calendar versioning (calver) by date ## v5.0.2 - fix #415: use git for matching prefixes to support the windows situation ## v5.0.1 - fix #509: support `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DISTRIBUTION_NAME}` for `pyproject.toml` ## v5.0.0 ### Breaking changes - fix #339: strict errors on missing scm when parsing a scm dir to avoid false version lookups - fix #337: if relative_to is a directory instead of a file, consider it as direct target instead of the containing folder and print a warning ### Fixed - fix #352: add support for generally ignoring specific vcs roots - fix #471: better error for version bump failing on complex but accepted tag - fix #479: raise indicative error when tags carry non-parsable information - Add `no-guess-dev` which does no next version guessing, just adds `.post1.devN` in case there are new commits after the tag - add python3.9 - enhance documentation - consider SOURCE_DATE_EPOCH for versioning - add a version_tuple to write_to templates - fix #321: add support for the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DISTRIBUTION_NAME}` env var to target the pretend key - fix #142: clearly list supported scm - fix #213: better error message for non-zero dev numbers in tags - fix #356: add git branch to version on describe failure ## v4.1.2 - disallow git tags without dots by default again - #449 ## v4.1.1 - drop jaraco.windows from pyproject.toml, allows for wheel builds on python2 ## v4.1.0 - include python 3.9 via the deadsnakes action - return release_branch_semver scheme (it got dropped in a bad rebase) - undo the devendoring of the samefile backport for python2.7 on windows - re-enable the building of universal wheels - fix handling of missing git/hg on python2.7 (python 3 exceptions where used) - correct the tox flake8 invocation - trigger builds on tags again ## v4.0.0 - Add `parentdir_prefix_version` to support installs from GitHub release tarballs. - use Coordinated Universal Time (UTC) - switch to github actions for ci - fix documentation for `tag_regex` and add support for single digit versions - document handling of enterprise distros with unsupported setuptools versions #312 - switch to declarative metadata - drop the internal copy of samefile and use a dependency on jaraco.windows on legacy systems - select git tags based on the presence of numbers instead of dots - enable getting a version form a parent folder prefix - add release-branch-semver version scheme - make global configuration available to version metadata - drop official support for python 3.4 ## v3.5.0 - add `no-local-version` local scheme and improve documentation for schemes ## v3.4.4 - fix #403: also sort out resource warnings when dealing with git file finding ## v3.4.3 - fix #399: ensure the git file finder terminates subprocess after reading archive ## v3.4.2 - fix #395: correctly transfer tag regex in the Configuration constructor - rollback \--first-parent for git describe as it turns out to be a regression for some users ## v3.4.1 - pull in #377 to fix #374: correctly set up the default version scheme for pyproject usage. this bugfix got missed when rushing the release. ## v3.4.0 - fix #181 - add support for projects built under setuptools declarative config by way of the setuptools.finalize_distribution_options hook in Setuptools 42. - fix #305 - ensure the git file finder closes file descriptors even when errors happen - fix #381 - clean out env vars from the git hook system to ensure correct function from within - modernize docs wrt importlib.metadata *edited* - use \--first-parent for git describe ## v3.3.3 - add eggs for python3.7 and 3.8 to the deploy ## v3.3.2 - fix #335 - fix python3.8 support and add builds for up to python3.8 ## v3.3.1 - fix #333 (regression from #198) - use a specific fallback root when calling fallbacks. Remove old hack that resets the root when fallback entrypoints are present. ## v3.3.0 - fix #198 by adding the `fallback_version` option, which sets the version to be used when everything else fails. ## v3.2.0 \* fix #303 and #283 by adding the option `git_describe_command` to allow the user to control the way that [git describe]{.title-ref} is called. ## v3.1.0 - fix #297 - correct the invocation in version_from_scm and deprecate it as its exposed by accident - fix #298 - handle git file listing on empty repositories - fix #268 - deprecate ScmVersion.extra ## v3.0.6 - fix #295 - correctly handle self install from tarballs ## v3.0.5 - fix #292 - match leading `V` character as well ## v3.0.4 - re-release of 3.0.3 after fixing the release process v3.0.3 (pulled from pypi due to a packaging issue) ====== - fix #286 - duo an oversight a helper function was returning a generator instead of a list ## v3.0.2 - fix a regression from tag parsing - support for multi-dashed prefixes - #284 ## v3.0.1 - fix a regression in setuptools_scm.git.parse - reorder arguments so the positional invocation from before works as expected #281 ## v3.0.0 - introduce pre-commit and use black - print the origin module to help testing - switch to src layout (breaking change) - no longer alias tag and parsed_version in order to support understanding a version parse failure - require parse results to be ScmVersion or None (breaking change) - fix #266 by requiring the prefix word to be a word again (breaking change as the bug allowed arbitrary prefixes while the original feature only allowed words\") - introduce an internal config object to allow the configuration for tag parsing and prefixes (thanks to \@punkadiddle for introducing it and passing it through) ## v2.1.0 - enhance docs for sphinx usage - add symlink support to file finder for git #247 (thanks Stéphane Bidoul) - enhance tests handling win32 (thanks Stéphane Bidoul) ## v2.0.0 - fix #237 - correct imports in code examples - improve mercurial commit detection (thanks Aaron) - breaking change: remove support for setuptools before parsed versions - reintroduce manifest as the travis deploy can\'t use the file finder - reconfigure flake8 for future compatibility with black - introduce support for branch name in version metadata and support a opt-in simplified semver version scheme ## v1.17.0 - fix regression in git support - use a function to ensure it works in egg installed mode - actually fail if file finding fails in order to see broken setups instead of generating broken dists (thanks Mehdi ABAAKOUK for both) ## v1.16.2 - fix regression in handling git export ignores (thanks Mehdi ABAAKOUK) ## v1.16.1 - fix regression in support for old setuptools versions (thanks Marco Clemencic) ## v1.16.0 - drop support for eol python versions - #214 - fix misuse in surrogate-escape api - add the node-and-timestamp local version scheme - respect git export ignores - avoid shlex.split on windows - fix #218 - better handling of mercurial edge-cases with tag commits being considered as the tagged commit - fix #223 - remove the dependency on the internal `SetuptoolsVersion` as it was removed after long-standing deprecation ## v1.15.7 - Fix #174 with #207: Reuse samefile backport as developed in jaraco.windows, and only use the backport where samefile is not available. ## v1.15.6 - fix #171 by unpinning the py version to allow a fixed one to get installed ## v1.15.5 - fix #167 by correctly respecting preformatted version metadata from PKG-INFO/EGG-INFO ## v1.15.4 - fix issue #164: iterate all found entry points to avoid errors when pip remakes egg-info - enhance self-use to enable pip install from github again ## v1.15.3 - bring back correctly getting our version in the own sdist, finalizes #114 - fix issue #150: strip local components of tags ## v1.15.2 - fix issue #128: return None when a scm specific parse fails in a worktree to ease parse reuse ## v1.15.1 - fix issue #126: the local part of any tags is discarded when guessing new versions - minor performance optimization by doing fewer git calls in the usual cases ## v1.15.0 - more sophisticated ignoring of mercurial tag commits when considering distance in commits (thanks Petre Mierlutiu) - fix issue #114: stop trying to be smart for the sdist and ensure it's always correctly using itself - update trove classifiers - fix issue #84: document using the installed package metadata for sphinx - fix issue #81: fail more gracious when git/hg are missing - address issue #93: provide an experimental api to customize behavior on shallow git repos a custom parse function may pick pre parse actions to do when using git ## v1.14.1 - fix #109: when detecting a dirty git workdir don't consider untracked file (this was a regression due to #86 in v1.13.1) - consider the distance 0 when the git node is unknown (happens when you haven't committed anything) ## v1.14.0 - publish bdist_egg for python 2.6, 2.7 and 3.3-3.5 - fix issue #107 - dont use node if it is None ## v1.13.1 - fix issue #86 - detect dirty git workdir without tags ## v1.13.0 - fix regression caused by the fix of #101 - assert types for version dumping - strictly pass all versions through parsed version metadata ## v1.12.0 - fix issue #97 - add support for mercurial plugins - fix issue #101 - write version cache even for pretend version (thanks anarcat for reporting and fixing) ## v1.11.1 - fix issue #88 - better docs for sphinx usage (thanks Jason) - fix issue #89 - use normpath to deal with windows (thanks Te-jé Rodgers for reporting and fixing) ## v1.11.0 - always run tag_to_version so in order to handle prefixes on old setuptools (thanks to Brian May) - drop support for python 3.2 - extend the error message on missing scm metadata (thanks Markus Unterwaditzer) - fix bug when using callable version_scheme (thanks Esben Haabendal) ## v1.10.1 - fix issue #73 - in hg pre commit merge, consider parent1 instead of failing ## v1.10.0 - add support for overriding the version number via the environment variable SETUPTOOLS_SCM_PRETEND_VERSION - fix issue #63 by adding the `--match` parameter to the git describe call and prepare the possibility of passing more options to scm backends - fix issue #70 and #71 by introducing the parse keyword to specify custom scm parsing, it's an expert feature, use with caution this change also introduces the setuptools_scm.parse_scm_fallback entrypoint which can be used to register custom archive fallbacks ## v1.9.0 - Add `relative_to` parameter to `get_version` function, fixes #44 per #45. ## v1.8.0 - fix issue with setuptools wrong version warnings being printed to standard out. User is informed now by distutils-warnings. - restructure root finding, we now reliably ignore outer scm and prefer PKG-INFO over scm, fixes #43 and #45 ## v1.7.0 - correct the url to github thanks David Szotten - enhance scm not found errors with a note on git tarballs thanks Markus - add support for `write_to_template` ## v1.6.0 - bail out early if the scm is missing this brings issues with git tarballs and older devpi-client releases to light, before we would let the setup stay at version 0.0, now there is a ValueError - properly raise errors on write_to misuse (thanks Te-jé Rodgers) ## v1.5.5 - Fix bug on Python 2 on Windows when environment has unicode fields. ## v1.5.4 - Fix bug on Python 2 when version is loaded from existing metadata. ## v1.5.3 - #28: Fix decoding error when PKG-INFO contains non-ASCII. ## v1.5.2 - add zip_safe flag ## v1.5.1 - fix file access bug I missed in 1.5 ## v1.5.0 - moved setuptools integration related code to own file - support storing version strings into a module/text file using the `write_to` configuration parameter ## v1.4.0 - proper handling for sdist - fix file-finder failure from windows - reshuffle docs ## v1.3.0 - support setuptools easy_install egg creation details by hardwire-ing the version in the sdist ## v1.2.0 - enhance self-use ## v1.1.0 - enable self-use ## v1.0.0 - documentation enhancements ## v0.26 - rename to setuptools_scm - split into package, add lots of entry points for extension - pluggable version schemes ## v0.25 - fix pep440 support this reshuffles the complete code for version guessing ## v0.24 - don't drop dirty flag on node finding - fix distance for dirty flagged versions - use dashes for time again, its normalisation with setuptools - remove the own version attribute, it was too fragile to test for - include file finding - handle edge cases around dirty tagged versions ## v0.23 - windows compatibility fix (thanks stefan) drop samefile since it`s missing in some python2 versions on windows - add tests to the source tarballs ## v0.22 - windows compatibility fix (thanks stefan) use samefile since it does path normalisation ## v0.21 - fix the own version attribute (thanks stefan) ## v0.20 - fix issue 11: always take git describe long format to avoid the source of the ambiguity - fix issue 12: add a `__version__` attribute via pkginfo ## v0.19 - configurable next version guessing - fix distance guessing (thanks stefan) setuptools-scm-9.2.2/LICENSE000066400000000000000000000017771507525030000155430ustar00rootroot00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. setuptools-scm-9.2.2/MANIFEST.in000066400000000000000000000010671507525030000162640ustar00rootroot00000000000000exclude *.nix exclude .pre-commit-config.yaml exclude changelog.d/* exclude .git_archival.txt exclude .readthedocs.yaml include *.py include testing/*.py include tox.ini include *.rst include LICENSE include *.toml include mypy.ini include testing/Dockerfile.* include src/setuptools_scm/.git_archival.txt include README.md include CHANGELOG.md recursive-include testing *.bash prune nextgen prune .cursor recursive-include docs *.md include docs/examples/version_scheme_code/*.py include docs/examples/version_scheme_code/*.toml include mkdocs.yml include uv.locksetuptools-scm-9.2.2/README.md000066400000000000000000000120141507525030000157770ustar00rootroot00000000000000# setuptools-scm [![github ci](https://github.com/pypa/setuptools-scm/actions/workflows/python-tests.yml/badge.svg)](https://github.com/pypa/setuptools-scm/actions/workflows/python-tests.yml) [![Documentation Status](https://readthedocs.org/projects/setuptools-scm/badge/?version=latest)](https://setuptools-scm.readthedocs.io/en/latest/?badge=latest) [![tidelift](https://tidelift.com/badges/package/pypi/setuptools-scm) ](https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme) ## about [setuptools-scm] extracts Python package versions from `git` or `hg` metadata instead of declaring them as the version argument or in a Source Code Managed (SCM) managed file. Additionally [setuptools-scm] provides `setuptools` with a list of files that are managed by the SCM
(i.e. it automatically adds all the SCM-managed files to the sdist).
Unwanted files must be excluded via `MANIFEST.in` or [configuring Git archive][git-archive-docs]. > **⚠️ Important:** Installing setuptools-scm automatically enables a file finder that includes **all SCM-tracked files** in your source distributions. This can be surprising if you have development files tracked in Git/Mercurial that you don't want in your package. Use `MANIFEST.in` to exclude unwanted files. See the [documentation] for details. ## `pyproject.toml` usage The preferred way to configure [setuptools-scm] is to author settings in a `tool.setuptools_scm` section of `pyproject.toml`. This feature requires setuptools 61 or later (recommended: >=80 for best compatibility). First, ensure that [setuptools-scm] is present during the project's build step by specifying it as one of the build requirements. ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" ``` That will be sufficient to require [setuptools-scm] for projects that support [PEP 518] like [pip] and [build]. [pip]: https://pypi.org/project/pip [build]: https://pypi.org/project/build [PEP 518]: https://peps.python.org/pep-0518/ To enable version inference, you need to set the version dynamically in the `project` section of `pyproject.toml`: ```toml title="pyproject.toml" [project] # version = "0.0.1" # Remove any existing version parameter. dynamic = ["version"] [tool.setuptools_scm] ``` !!! note "Simplified Configuration" Starting with setuptools-scm 8.1+, if `setuptools_scm` (or `setuptools-scm`) is present in your `build-system.requires`, the `[tool.setuptools_scm]` section becomes optional! You can now enable setuptools-scm with just: ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" [project] dynamic = ["version"] ``` The `[tool.setuptools_scm]` section is only needed if you want to customize configuration options. Additionally, a version file can be written by specifying: ```toml title="pyproject.toml" [tool.setuptools_scm] version_file = "pkg/_version.py" ``` Where `pkg` is the name of your package. If you need to confirm which version string is being generated or debug the configuration, you can install [setuptools-scm] directly in your working environment and run: ```console $ python -m setuptools_scm # To explore other options, try: $ python -m setuptools_scm --help ``` For further configuration see the [documentation]. [setuptools-scm]: https://github.com/pypa/setuptools-scm [documentation]: https://setuptools-scm.readthedocs.io/ [git-archive-docs]: https://setuptools-scm.readthedocs.io/en/stable/usage/#builtin-mechanisms-for-obtaining-version-numbers ## Interaction with Enterprise Distributions Some enterprise distributions like RHEL7 ship rather old setuptools versions. In those cases its typically possible to build by using an sdist against `setuptools-scm<2.0`. As those old setuptools versions lack sensible types for versions, modern [setuptools-scm] is unable to support them sensibly. It's strongly recommended to build a wheel artifact using modern Python and setuptools, then installing the artifact instead of trying to run against old setuptools versions. !!! note "Legacy Setuptools Support" While setuptools-scm recommends setuptools >=80, it maintains compatibility with setuptools 61+ to support legacy deployments that cannot easily upgrade. Support for setuptools <80 is deprecated and will be removed in a future release. This allows enterprise environments and older CI/CD systems to continue using setuptools-scm while still encouraging adoption of newer versions. ## Code of Conduct Everyone interacting in the [setuptools-scm] project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [PSF Code of Conduct]. [PSF Code of Conduct]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md ## Security Contact To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. setuptools-scm-9.2.2/_own_version_helper.py000066400000000000000000000037401507525030000211460ustar00rootroot00000000000000""" this module is a hack only in place to allow for setuptools to use the attribute for the versions it works only if the backend-path of the build-system section from pyproject.toml is respected """ from __future__ import annotations import logging import os from typing import Callable from setuptools import build_meta as build_meta from setuptools_scm import Configuration from setuptools_scm import _types as _t from setuptools_scm import get_version from setuptools_scm import git from setuptools_scm import hg from setuptools_scm.fallbacks import parse_pkginfo from setuptools_scm.version import ScmVersion from setuptools_scm.version import get_local_node_and_date from setuptools_scm.version import get_no_local_node from setuptools_scm.version import guess_next_dev_version log = logging.getLogger("setuptools_scm") # todo: take fake entrypoints from pyproject.toml try_parse: list[Callable[[_t.PathT, Configuration], ScmVersion | None]] = [ parse_pkginfo, git.parse, hg.parse, git.parse_archival, hg.parse_archival, ] def parse(root: str, config: Configuration) -> ScmVersion | None: for maybe_parse in try_parse: try: parsed = maybe_parse(root, config) except OSError as e: log.warning("parse with %s failed with: %s", maybe_parse, e) else: if parsed is not None: return parsed return None def scm_version() -> str: # Use no-local-version if SETUPTOOLS_SCM_NO_LOCAL is set (for CI uploads) local_scheme = ( get_no_local_node if os.environ.get("SETUPTOOLS_SCM_NO_LOCAL") else get_local_node_and_date ) return get_version( relative_to=__file__, parse=parse, version_scheme=guess_next_dev_version, local_scheme=local_scheme, ) version: str def __getattr__(name: str) -> str: if name == "version": global version version = scm_version() return version raise AttributeError(name) setuptools-scm-9.2.2/docs/000077500000000000000000000000001507525030000154525ustar00rootroot00000000000000setuptools-scm-9.2.2/docs/changelog.md000066400000000000000000000000541507525030000177220ustar00rootroot00000000000000{% include-markdown "../CHANGELOG.md" %} setuptools-scm-9.2.2/docs/config.md000066400000000000000000000260101507525030000172400ustar00rootroot00000000000000# Configuration ## When is configuration needed? setuptools-scm provides flexible activation options: ### Simplified Activation (No Configuration Needed) For basic usage, use the `simple` extra with no configuration: ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm[simple]>=8"] [project] dynamic = ["version"] ``` This automatically enables version inference with default settings. ### Explicit Configuration (Full Control) Use the `[tool.setuptools_scm]` section when you need to: - Write version files (`version_file`) - Customize version schemes (`version_scheme`, `local_scheme`) - Set custom tag patterns (`tag_regex`) - Configure fallback behavior (`fallback_version`) - Or any other non-default behavior ## configuration parameters Configuration parameters can be configured in `pyproject.toml` or `setup.py`. Callables or other Python objects have to be passed in `setup.py` (via the `use_scm_version` keyword argument). `root : Path | PathLike[str]` : Relative path to the SCM root, defaults to `.` and is relative to the file path passed in `relative_to` `version_scheme : str | Callable[[ScmVersion], str]` : Configures how the version number is constructed; either an entrypoint name or a callable. See [Version number construction](extending.md#setuptools_scmversion_scheme) for predefined implementations. `local_scheme : str | Callable[[ScmVersion], str]` : Configures how the local component of the version (the optional part after the `+`) is constructed; either an entrypoint name or a callable. See [Version number construction](extending.md#setuptools_scmlocal_scheme) for predefined implementations. `version_file: Path | PathLike[str] | None = None` : A path to a file that gets replaced with a file containing the current version. It is ideal for creating a ``_version.py`` file within the package, typically used to avoid using `importlib.metadata` (which adds some overhead). !!! warning "" Only files with `.py` and `.txt` extensions have builtin templates, for other file types it is necessary to provide `version_file_template`. `version_file_template: str | None = None` : A new-style format string taking `version`, `scm_version` and `version_tuple` as parameters. `version` is the generated next_version as string, `version_tuple` is a tuple of split numbers/strings and `scm_version` is the `ScmVersion` instance the current `version` was rendered from `write_to: Pathlike[str] | Path | None = None` : (deprecated) legacy option to create a version file relative to the scm root it's broken for usage from a sdist and fixing it would be a fatal breaking change, use `version_file` instead. `relative_to: Path|Pathlike[str] = "pyproject.toml"` : A file/directory from which the root can be resolved. Typically called by a script or module that is not in the root of the repository to point `setuptools_scm` at the root of the repository by supplying `__file__`. `tag_regex: str|Pattern[str]` : A Python regex string to extract the version part from any SCM tag. The regex needs to contain either a single match group, or a group named `version`, that captures the actual version information. Defaults to the value of [setuptools_scm._config.DEFAULT_TAG_REGEX][] which supports tags with optional "v" prefix (recommended), project prefixes, and various version formats. !!! tip The default regex supports common tag formats like `v1.0.0`, `myproject-v1.0.0`, and `1.0.0`. For best practices on tag naming, see [Version Tag Formats](usage.md#version-tag-formats). `parentdir_prefix_version: str|None = None` : If the normal methods for detecting the version (SCM version, sdist metadata) fail, and the parent directory name starts with `parentdir_prefix_version`, then this prefix is stripped and the rest of the parent directory name is matched with `tag_regex` to get a version string. If this parameter is unset (the default), then this fallback is not used. This was intended to cover GitHub's "release tarballs", which extract into directories named `projectname-tag/` (in which case `parentdir_prefix_version` can be set e.g. to `projectname-`). `fallback_version: str | None = None` : A version string that will be used if no other method for detecting the version worked (e.g., when using a tarball with no metadata). If this is unset (the default), `setuptools-scm` will error if it fails to detect the version. `fallback_root: Path | PathLike[str] = "."` : The directory to use when SCM metadata is not available (e.g., in extracted archives like PyPI tarballs). This is particularly useful for legacy configurations that need to work both in development (with SCM metadata) and from archives (without SCM metadata). Defaults to the current directory. When SCM metadata is present, the `root` parameter is used; when it's not available, `fallback_root` is used instead. This allows the same configuration to work in both scenarios without modification. `parse: Callable[[Path, Config], ScmVersion] | None = None` : A function that will be used instead of the discovered SCM for parsing the version. Use with caution, this is a function for advanced use and you should be familiar with the `setuptools-scm` internals to use it. `scm.git.describe_command` : This command will be used instead the default `git describe --long` command. Defaults to the value set by [setuptools_scm.git.DEFAULT_DESCRIBE][] `scm.git.pre_parse` : A string specifying which git pre-parse function to use before parsing version information. Available options: - `"warn_on_shallow"` (default): Warns when the repository is shallow - `"fail_on_shallow"`: Fails with an error when the repository is shallow - `"fetch_on_shallow"`: Automatically fetches to rectify shallow repositories - `"fail_on_missing_submodules"`: Fails when submodules are defined but not initialized The `"fail_on_missing_submodules"` option is useful to prevent packaging incomplete projects when submodules are required for a complete build. Note: This setting is overridden by any explicit `pre_parse` parameter passed to the git parse function. `git_describe_command` (deprecated) : **Deprecated since 8.4.0**: Use `scm.git.describe_command` instead. This field is maintained for backward compatibility but will issue a deprecation warning when used. `normalize` : A boolean flag indicating if the version string should be normalized. Defaults to `True`. Setting this to `False` is equivalent to setting `version_cls` to [setuptools_scm.NonNormalizedVersion][] `version_cls: type|str = packaging.version.Version` : An optional class used to parse, verify and possibly normalize the version string. Its constructor should receive a single string argument, and its `str` should return the normalized version string to use. This option can also receive a class qualified name as a string. The [setuptools_scm.NonNormalizedVersion][] convenience class is provided to disable the normalization step done by `packaging.version.Version`. If this is used while `setuptools-scm` is integrated in a setuptools packaging process, the non-normalized version number will appear in all files (see `version_file` note). !!! note "normalization still applies to artifact filenames" Setuptools will still normalize it to create the final distribution, so as to stay compliant with the python packaging standards. ## environment variables `SETUPTOOLS_SCM_PRETEND_VERSION` : used as the primary source for the version number in which case it will be an unparsed string !!! warning "" it is strongly recommended to use distribution-specific pretend versions (see below). `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}` : used as the primary source for the version number, in which case it will be an unparsed string. Specifying distribution-specific pretend versions will avoid possible collisions with third party distributions also using ``setuptools-scm`` the dist name normalization follows adapted PEP 503 semantics, with one or more of ".-\_" being replaced by a single "\_", and the name being upper-cased this will take precedence over ``SETUPTOOLS_SCM_PRETEND_VERSION`` `SETUPTOOLS_SCM_DEBUG` : enable the debug logging `SOURCE_DATE_EPOCH` : used as the timestamp from which the ``node-and-date`` and ``node-and-timestamp`` local parts are derived, otherwise the current time is used (https://reproducible-builds.org/docs/source-date-epoch/) `SETUPTOOLS_SCM_IGNORE_VCS_ROOTS` : a ``os.pathsep`` separated list of directory names to ignore for root finding `SETUPTOOLS_SCM_HG_COMMAND` : command used for running Mercurial (defaults to ``hg``) for example, set this to ``chg`` to reduce start-up overhead of Mercurial ## automatic file inclusion !!! warning "Setuptools File Finder Integration" `setuptools-scm` automatically registers a setuptools file finder that includes all SCM-tracked files in source distributions. This behavior is **always active** when setuptools-scm is installed, regardless of whether you use it for versioning. **How it works:** `setuptools-scm` provides a `setuptools.file_finders` entry point that: 1. Automatically discovers SCM-managed files (Git, Mercurial) 2. Includes them in source distributions (`python -m build --sdist`) 3. Works for `include_package_data = True` in package building **Entry point registration:** ```toml [project.entry-points."setuptools.file_finders"] setuptools_scm = "setuptools_scm._file_finders:find_files" ``` **Files included by default:** - All files tracked by Git (`git ls-files`) - All files tracked by Mercurial (`hg files`) - Includes: source code, documentation, tests, config files, etc. - Excludes: untracked files, files in `.gitignore`/`.hgignore` **Controlling inclusion:** Use `MANIFEST.in` to override the automatic behavior: ```text title="MANIFEST.in" # Exclude development files exclude .pre-commit-config.yaml exclude tox.ini global-exclude *.pyc __pycache__/ # Exclude entire directories prune docs/ prune testing/ # Include non-SCM files include data/important.json ``` **Debugging file inclusion:** ```bash # List files that will be included python -m setuptools_scm ls # Build and inspect sdist contents python -m build --sdist tar -tzf dist/package-*.tar.gz ``` !!! note "Cannot be disabled" The file finder cannot be disabled through configuration - it's automatically active when setuptools-scm is installed. If you need to disable it completely, you must remove setuptools-scm from your build environment (which also means you can't use it for versioning). ## api reference ### constants ::: setuptools_scm._config.DEFAULT_TAG_REGEX options: heading_level: 4 ::: setuptools_scm.git.DEFAULT_DESCRIBE options: heading_level: 4 ### the configuration class ::: setuptools_scm.Configuration options: heading_level: 4 setuptools-scm-9.2.2/docs/customizing.md000066400000000000000000000035231507525030000203520ustar00rootroot00000000000000# Customizing ## providing project local version schemes As PEP 621 provides no way to specify local code as a build backend plugin, setuptools-scm has to piggyback on setuptools for passing functions over. To facilitate that one needs to write a `setup.py` file and pass partial setuptools-scm configuration in via the use_scm_version keyword. It's strongly recommended to experiment with using stock version schemes or creating plugins as package. (This recommendation will change if there ever is something like build-time entrypoints). ``` { .python title="setup.py" file="docs/examples/version_scheme_code/setup.py" } # we presume installed build dependencies from __future__ import annotations from setuptools import setup from setuptools_scm import ScmVersion def myversion_func(version: ScmVersion) -> str: from setuptools_scm.version import guess_next_version return version.format_next_version(guess_next_version, "{guessed}b{distance}") setup(use_scm_version={"version_scheme": myversion_func}) ``` ``` { .toml title="pyproject.toml" file="docs/examples/version_scheme_code/pyproject.toml" } [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] # setuptools>=61 minimum, >=80 recommended build-backend = "setuptools.build_meta" [project] name = "scm-example" dynamic = [ "version", ] [tool.setuptools_scm] ``` - [ ] add a build block that adds example output ## Importing in setup.py With the pep 517/518 build backend, setuptools-scm is importable from `setup.py` ``` { .python title="setup.py" } from setuptools import setup from setuptools_scm.version import get_local_dirty_tag def clean_scheme(version): return get_local_dirty_tag(version) if version.dirty else '+clean' setup(use_scm_version={'local_scheme': clean_scheme}) ``` ## alternative version classes ::: setuptools_scm.NonNormalizedVersion setuptools-scm-9.2.2/docs/examples/000077500000000000000000000000001507525030000172705ustar00rootroot00000000000000setuptools-scm-9.2.2/docs/examples/version_scheme_code/000077500000000000000000000000001507525030000232735ustar00rootroot00000000000000setuptools-scm-9.2.2/docs/examples/version_scheme_code/pyproject.toml000066400000000000000000000005151507525030000262100ustar00rootroot00000000000000# ~/~ begin <>[init] [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] # setuptools>=61 minimum, >=80 recommended build-backend = "setuptools.build_meta" [project] name = "scm-example" dynamic = [ "version", ] [tool.setuptools_scm] # ~/~ end setuptools-scm-9.2.2/docs/examples/version_scheme_code/setup.py000066400000000000000000000007551507525030000250140ustar00rootroot00000000000000# ~/~ begin <>[init] # we presume installed build dependencies from __future__ import annotations from setuptools import setup from setuptools_scm import ScmVersion def myversion_func(version: ScmVersion) -> str: from setuptools_scm.version import guess_next_version return version.format_next_version(guess_next_version, "{guessed}b{distance}") setup(use_scm_version={"version_scheme": myversion_func}) # ~/~ end setuptools-scm-9.2.2/docs/extending.md000066400000000000000000000127311507525030000177650ustar00rootroot00000000000000# Extending setuptools-scm `setuptools-scm` uses [entry-point][entry-point] based hooks to extend its default capabilities. [entry-point]: https://packaging.python.org/en/latest/specifications/entry-points/ ## Adding a new SCM `setuptools-scm` provides two entrypoints for adding new SCMs: `setuptools_scm.parse_scm` : A function used to parse the metadata of the current workdir using the name of the control directory/file of your SCM as the entrypoint's name. E.g. for the built-in entrypoint for Git the entrypoint is named `.git` and references `setuptools_scm.git:parse` The return value MUST be a [`setuptools_scm.version.ScmVersion`][] instance created by the function [`setuptools_scm.version.meta`][]. `setuptools_scm.files_command` : Either a string containing a shell command that prints all SCM managed files in its current working directory or a callable, that given a pathname will return that list. Also uses then name of your SCM control directory as name of the entrypoint. ### api reference for scm version objects ::: setuptools_scm.version.ScmVersion options: show_root_heading: yes heading_level: 4 ::: setuptools_scm.version.meta options: show_root_heading: yes heading_level: 4 ## Version number construction ### `setuptools_scm.version_scheme` Configures how the version number is constructed given a [ScmVersion][setuptools_scm.version.ScmVersion] instance and should return a string representing the version. ### Available implementations `guess-next-dev (default)` : Automatically guesses the next development version (default). Guesses the upcoming release by incrementing the pre-release segment if present, otherwise by incrementing the micro segment. Then appends `.devN`. In case the tag ends with `.dev0` the version is not bumped and custom `.devN` versions will trigger an error. **Examples:** - Tag `v1.0.0` → version `1.0.1.dev0` (if dirty or distance > 0) - Tag `v1.0.0` → version `1.0.0` (if exact match) `calver-by-date` : Calendar versioning scheme that generates versions based on dates. Uses the format `YY.MM.DD.patch` or `YYYY.MM.DD.patch` depending on the existing tag format. If the commit is on the same date as the latest tag, increments the patch number. Otherwise, uses the current date with patch 0. Supports branch-specific versioning for release branches. **Examples:** - Tag `v23.01.15.0` on same day → version `23.01.15.1.devN` - Tag `v23.01.15.0` on different day (e.g., 2023-01-16) → version `23.01.16.0.devN` - Tag `v2023.01.15.0` → uses 4-digit year format for new versions `no-guess-dev` : Does no next version guessing, just adds `.post1.devN`. This is the recommended replacement for the deprecated `post-release` scheme. **Examples:** - Tag `v1.0.0` → version `1.0.0.post1.devN` (if distance > 0) - Tag `v1.0.0` → version `1.0.0` (if exact match) `only-version` : Only use the version from the tag, as given. !!! warning "This means version is no longer pseudo unique per commit" **Examples:** - Tag `v1.0.0` → version `1.0.0` (always, regardless of distance or dirty state) `post-release (deprecated)` : Generates post release versions (adds `.postN`) after review of the version number pep this is considered a bad idea as post releases are intended to be chosen not autogenerated. !!! warning "the recommended replacement is `no-guess-dev`" **Examples:** - Tag `1.0.0` → version `1.0.0.postN` (where N is the distance) `python-simplified-semver` : Basic semantic versioning. Guesses the upcoming release by incrementing the minor segment and setting the micro segment to zero if the current branch contains the string `feature`, otherwise by incrementing the micro version. Then appending `.devN`. This scheme is not compatible with pre-releases. **Examples:** - Tag `1.0.0` on non-feature branch → version `1.0.1.devN` - Tag `1.0.0` on feature branch → version `1.1.0.devN` `release-branch-semver` : Semantic versioning for projects with release branches. The same as `guess-next-dev` (incrementing the pre-release or micro segment) however when on a release branch: a branch whose name (ignoring namespace) parses as a version that matches the most recent tag up to the minor segment. Otherwise if on a non-release branch, increments the minor segment and sets the micro segment to zero, then appends `.devN` Namespaces are unix pathname separated parts of a branch/tag name. **Examples:** - Tag `1.0.0` on release branch `release-1.0` → version `1.0.1.devN` - Tag `1.0.0` on release branch `release/v1.0` → version `1.0.1.devN` - Tag `1.0.0` on development branch → version `1.1.0.devN` ### `setuptools_scm.local_scheme` Configures how the local part of a version is rendered given a [ScmVersion][setuptools_scm.version.ScmVersion] instance and should return a string representing the local version. Dates and times are in Coordinated Universal Time (UTC), because as part of the version, they should be location independent. #### Available implementations `node-and-date (default)` : adds the node on dev versions and the date on dirty workdir `node-and-timestamp` : like `node-and-date` but with a timestamp of the form `%Y%m%d%H%M%S` instead `dirty-tag` : adds `+dirty` if the current workdir has changes `no-local-version` : omits local version, useful e.g. because pypi does not support it setuptools-scm-9.2.2/docs/index.md000066400000000000000000000053361507525030000171120ustar00rootroot00000000000000# About `setuptools-scm` extracts Python package versions from `git` or `hg` metadata instead of declaring them as the version argument or in a Source Code Managed (SCM) managed file. Additionally `setuptools-scm` provides `setuptools` with a list of files that are managed by the SCM (i.e. it automatically adds all the SCM-managed files to the sdist). Unwanted files must be excluded via `MANIFEST.in` or [configuring Git archive][git-archive-docs]. !!! warning "Automatic File Inclusion Behavior" **Important:** Simply installing `setuptools-scm` as a build dependency will automatically enable its file finder, which includes **all SCM-tracked files** in your source distributions. This happens even if you're not using setuptools-scm for versioning. - ✅ **Expected**: All Git/Mercurial tracked files will be included in your sdist - ⚠️ **Surprise**: This includes development files, configs, tests, docs, etc. - 🛠️ **Control**: Use `MANIFEST.in` to exclude unwanted files See the [File Finder Documentation](usage.md#file-finders-hook-makes-most-of-manifestin-unnecessary) for details. [git-archive-docs]: usage.md#builtin-mechanisms-for-obtaining-version-numbers ## Basic usage ### With setuptools Note: `setuptools-scm>=8` intentionally doesn't depend on setuptools to ease non-setuptools usage. Please ensure a recent version of setuptools is installed (minimum: >=61, recommended: >=80 for best compatibility). Support for setuptools <80 is deprecated and will be removed in a future release. **Simplified setup (recommended for basic usage):** ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm[simple]>=8"] build-backend = "setuptools.build_meta" [project] name = "example" # Important: Remove any existing version declaration # version = "0.0.1" dynamic = ["version"] # No additional configuration needed! ``` **With custom configuration:** ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" [project] name = "example" dynamic = ["version"] [tool.setuptools_scm] # Custom configuration options go here ``` !!! tip "Recommended Tag Format" Use the **"v" prefix** for your version tags for best compatibility: ```bash git tag v1.0.0 git tag v1.1.0 git tag v2.0.0-rc1 ``` This is a widely adopted convention that works well with setuptools-scm and other tools. See the [Version Tag Formats](usage.md#version-tag-formats) section for more details. ### With hatch [Hatch-vcs](https://github.com/ofek/hatch-vcs) integrates with setuptools-scm but provides its own configuration options, please see its [documentation](https://github.com/ofek/hatch-vcs#readme) setuptools-scm-9.2.2/docs/integrations.md000066400000000000000000000236461507525030000205150ustar00rootroot00000000000000# Integrations ## ReadTheDocs ### Avoid having a dirty Git index When building documentation on ReadTheDocs, file changes during the build process can cause setuptools-scm to detect a "dirty" working directory. To avoid this issue, ReadTheDocs recommends using build customization to clean the Git state after checkout: ```yaml title=".readthedocs.yaml" version: 2 build: os: "ubuntu-22.04" tools: python: "3.10" jobs: post_checkout: # Avoid setuptools-scm dirty Git index issues - git reset --hard HEAD - git clean -fdx ``` This ensures a clean Git working directory before setuptools-scm detects the version, preventing unwanted local version components. Reference: [ReadTheDocs Build Customization - Avoid having a dirty Git index](https://docs.readthedocs.com/platform/stable/build-customization.html#avoid-having-a-dirty-git-index) ### Enforce fail on shallow repositories ReadTheDocs may sometimes use shallow Git clones that lack the full history needed for proper version detection. You can use setuptools-scm's environment variable override system to enforce `fail_on_shallow` when building on ReadTheDocs: ```yaml title=".readthedocs.yaml" version: 2 build: os: "ubuntu-22.04" tools: python: "3.10" jobs: post_checkout: # Avoid setuptools-scm dirty Git index issues - git reset --hard HEAD - git clean -fdx # Enforce fail_on_shallow for setuptools-scm - export SETUPTOOLS_SCM_OVERRIDES_FOR_${READTHEDOCS_PROJECT//-/_}='{scm.git.pre_parse="fail_on_shallow"}' ``` This configuration uses the `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` environment variable to override the `scm.git.pre_parse` setting specifically for your project when building on ReadTheDocs, forcing setuptools-scm to fail with a clear error if the repository is shallow. ## CI/CD and Package Publishing ### Publishing to PyPI from CI/CD When publishing packages to PyPI or test-PyPI from CI/CD pipelines, you often need to remove local version components that are not allowed on public package indexes according to [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers). setuptools-scm provides the `no-local-version` local scheme and environment variable overrides to handle this scenario cleanly. #### The Problem By default, setuptools-scm generates version numbers like: - `1.2.3.dev4+g1a2b3c4d5` (development version with git hash) - `1.2.3+dirty` (dirty working directory) These local version components (`+g1a2b3c4d5`, `+dirty`) prevent uploading to PyPI. #### The Solution Use the `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` environment variable to override the `local_scheme` to `no-local-version` when building for upload to PyPI. ### GitHub Actions Example Here's a complete GitHub Actions workflow that: - Runs tests on all branches - Uploads development versions to test-PyPI from feature branches - Uploads development versions to PyPI from the main branch (with no-local-version) - Uploads tagged releases to PyPI (using exact tag versions) ```yaml title=".github/workflows/ci.yml" name: CI/CD on: push: branches: ["main", "develop"] pull_request: branches: ["main", "develop"] release: types: [published] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 with: # Fetch full history for setuptools-scm fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install build pytest pip install -e . - name: Run tests run: pytest publish-test-pypi: needs: test runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref != 'refs/heads/main' env: # Replace MYPACKAGE with your actual package name (normalized) # For package "my-awesome.package", use "MY_AWESOME_PACKAGE" SETUPTOOLS_SCM_OVERRIDES_FOR_MYPACKAGE: '{"local_scheme": "no-local-version"}' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.11" - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build package run: python -m build - name: Upload to test-PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ password: ${{ secrets.TEST_PYPI_API_TOKEN }} publish-pypi: needs: test runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' env: # Replace MYPACKAGE with your actual package name (normalized) SETUPTOOLS_SCM_OVERRIDES_FOR_MYPACKAGE: '{"local_scheme": "no-local-version"}' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.11" - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build package run: python -m build - name: Upload to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} publish-release: needs: test runs-on: ubuntu-latest if: github.event_name == 'release' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.11" - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build package run: python -m build - name: Upload to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} ``` ### GitLab CI Example Here's an equivalent GitLab CI configuration: ```yaml title=".gitlab-ci.yml" stages: - test - publish variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" cache: paths: - .cache/pip/ before_script: - python -m pip install --upgrade pip test: stage: test image: python:3.11 script: - pip install build pytest - pip install -e . - pytest parallel: matrix: - PYTHON_VERSION: ["3.8", "3.9", "3.10", "3.11", "3.12"] image: python:${PYTHON_VERSION} publish-test-pypi: stage: publish image: python:3.11 variables: TWINE_USERNAME: __token__ TWINE_PASSWORD: $TEST_PYPI_API_TOKEN # Replace MYPACKAGE with your actual package name (normalized) SETUPTOOLS_SCM_OVERRIDES_FOR_MYPACKAGE: '{"local_scheme": "no-local-version"}' script: - pip install build twine - python -m build - twine upload --repository testpypi dist/* rules: - if: $CI_COMMIT_BRANCH != "main" && $CI_PIPELINE_SOURCE == "push" publish-pypi: stage: publish image: python:3.11 variables: TWINE_USERNAME: __token__ TWINE_PASSWORD: $PYPI_API_TOKEN # Replace MYPACKAGE with your actual package name (normalized) SETUPTOOLS_SCM_OVERRIDES_FOR_MYPACKAGE: '{"local_scheme": "no-local-version"}' script: - pip install build twine - python -m build - twine upload dist/* rules: - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" publish-release: stage: publish image: python:3.11 variables: TWINE_USERNAME: __token__ TWINE_PASSWORD: $PYPI_API_TOKEN script: - pip install build twine - python -m build - twine upload dist/* rules: - if: $CI_COMMIT_TAG ``` ### Configuration Details #### Environment Variable Format The environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` must be set where: 1. **`${DIST_NAME}`** is your package name normalized according to PEP 503: - Convert to uppercase - Replace hyphens and dots with underscores - Examples: `my-package` → `MY_PACKAGE`, `my.package` → `MY_PACKAGE` 2. **Value** must be a valid TOML inline table format: ```bash SETUPTOOLS_SCM_OVERRIDES_FOR_MYPACKAGE='{"local_scheme": "no-local-version"}' ``` #### Alternative Approaches **Option 1: pyproject.toml Configuration** Instead of environment variables, you can configure this in your `pyproject.toml`: ```toml title="pyproject.toml" [tool.setuptools_scm] # Use no-local-version by default for CI builds local_scheme = "no-local-version" ``` However, the environment variable approach is preferred for CI/CD as it allows different schemes for local development vs. CI builds. #### Version Examples **Development versions from main branch** (with `local_scheme = "no-local-version"`): - Development commit: `1.2.3.dev4+g1a2b3c4d5` → `1.2.3.dev4` ✅ (uploadable to PyPI) - Dirty working directory: `1.2.3+dirty` → `1.2.3` ✅ (uploadable to PyPI) **Tagged releases** (without overrides, using default local scheme): - Tagged commit: `1.2.3` → `1.2.3` ✅ (uploadable to PyPI) - Tagged release on dirty workdir: `1.2.3+dirty` → `1.2.3+dirty` ❌ (should not happen in CI) ### Security Notes - Store PyPI API tokens as repository secrets - Use separate tokens for test-PyPI and production PyPI - Consider using [Trusted Publishers](https://docs.pypi.org/trusted-publishers/) for enhanced security ### Troubleshooting **Package name normalization**: If your override isn't working, verify the package name normalization: ```python import re dist_name = "my-awesome.package" normalized = re.sub(r"[-_.]+", "-", dist_name) env_var_name = normalized.replace("-", "_").upper() print(f"SETUPTOOLS_SCM_OVERRIDES_FOR_{env_var_name}") # Output: SETUPTOOLS_SCM_OVERRIDES_FOR_MY_AWESOME_PACKAGE ``` **Fetch depth**: Always use `fetch-depth: 0` in GitHub Actions to ensure setuptools-scm has access to the full git history for proper version calculation.setuptools-scm-9.2.2/docs/overrides.md000066400000000000000000000062461507525030000200060ustar00rootroot00000000000000# Overrides ## pretend versions setuptools-scm provides a mechanism to override the version number build time. the environment variable `SETUPTOOLS_SCM_PRETEND_VERSION` is used as the override source for the version number unparsed string. to be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}` where the dist name normalization follows adapted PEP 503 semantics. ## pretend metadata setuptools-scm provides a mechanism to override individual version metadata fields at build time. The environment variable `SETUPTOOLS_SCM_PRETEND_METADATA` accepts a TOML inline table with field overrides for the ScmVersion object. To be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_METADATA_FOR_${DIST_NAME}` where the dist name normalization follows adapted PEP 503 semantics. ### Supported fields The following ScmVersion fields can be overridden: - `distance` (int): Number of commits since the tag - `node` (str): The commit hash/node identifier - `dirty` (bool): Whether the working directory has uncommitted changes - `branch` (str): The branch name - `node_date` (date): The date of the commit (TOML date format: `2024-01-15`) - `time` (datetime): The version timestamp (TOML datetime format) - `preformatted` (bool): Whether the version string is preformatted - `tag`: The version tag (can be string or version object) ### Examples Override commit hash and distance: ```bash export SETUPTOOLS_SCM_PRETEND_METADATA='{node="g1337beef", distance=4}' ``` Override multiple fields with proper TOML types: ```bash export SETUPTOOLS_SCM_PRETEND_METADATA='{node="gabcdef12", distance=7, dirty=true, node_date=2024-01-15}' ``` Use with a specific package: ```bash export SETUPTOOLS_SCM_PRETEND_METADATA_FOR_MY_PACKAGE='{node="g1234567", distance=2}' ``` !!! note "Node ID Prefixes" Node IDs must include the appropriate SCM prefix: - Use `g` prefix for git repositories (e.g., `g1a2b3c4d5`) - Use `h` prefix for mercurial repositories (e.g., `h1a2b3c4d5`) This ensures consistency with setuptools-scm's automatic node ID formatting. ### Use case: CI/CD environments This is particularly useful for solving issues where version file templates need access to commit metadata that may not be available in certain build environments: ```toml [tool.setuptools_scm] version_file = "src/mypackage/_version.py" version_file_template = ''' version = "{version}" commit_hash = "{scm_version.node}" commit_count = {scm_version.distance} ''' ``` With pretend metadata, you can ensure the template gets the correct values: ```bash export SETUPTOOLS_SCM_PRETEND_VERSION="1.2.3.dev4+g1337beef" export SETUPTOOLS_SCM_PRETEND_METADATA='{node="g1337beef", distance=4}' ``` ## config overrides setuptools-scm parses the environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` as a toml inline map to override the configuration data from `pyproject.toml`. ## subprocess timeouts The environment variable `SETUPTOOLS_SCM_SUBPROCESS_TIMEOUT` allows to override the subprocess timeout. The default is 40 seconds and should work for most needs. However, users with git lfs + windows reported situations where this was not enough. setuptools-scm-9.2.2/docs/usage.md000066400000000000000000000537621507525030000171150ustar00rootroot00000000000000# Usage ## At build time !!! note "Setuptools Version Requirements" setuptools-scm requires setuptools 61 or later (minimum), but recommends >=80 for best compatibility. Support for setuptools <80 is deprecated and will be removed in a future release. The examples below use `setuptools>=80` as the recommended version. There are three ways to enable `setuptools-scm` at build time: ### Simplified Activation (new) For basic usage without custom configuration, use the `simple` extra: ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm[simple]>=8"] build-backend = "setuptools.build_meta" [project] # version = "0.0.1" # Remove any existing version parameter. dynamic = ["version"] # No [tool.setuptools_scm] section needed for basic usage! ``` This streamlined approach automatically enables version inference when: - `setuptools-scm[simple]` is listed in `build-system.requires` - `version` is included in `project.dynamic` !!! tip "When to use simplified activation" Use simplified activation when you: - Want basic SCM version inference with default settings - Don't need custom version schemes or file writing - Prefer minimal configuration Upgrade to explicit configuration if you need customization. ### Explicit Configuration (full control) Add a `tool.setuptools_scm` section for custom configuration: ```toml title="pyproject.toml" [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" [project] # version = "0.0.1" # Remove any existing version parameter. dynamic = ["version"] [tool.setuptools_scm] # Configure custom options here (version schemes, file writing, etc.) version_file = "src/mypackage/_version.py" # Example: Git-specific configuration [tool.setuptools_scm.scm.git] pre_parse = "fail_on_missing_submodules" # Fail if submodules are not initialized describe_command = "git describe --dirty --tags --long --exclude *js*" # Custom describe command ``` Projects must support PEP 518 ([pip](https://pypi.org/project/pip) and [pep517](https://pypi.org/project/pep517/)). Tools that still invoke `setup.py` must ensure build requirements are installed. ### Using the setup keyword Alternatively, enable `setuptools-scm` via the `use_scm_version` keyword in `setup.py`. This also counts as an explicit opt-in and does not require a tool section. !!! note "Legacy simplified activation" Previous versions had a "simplified" activation where listing `setuptools_scm` in `build-system.requires` together with `project.dynamic = ["version"]` would auto-enable version inference. This behavior was removed due to regressions and ambiguous activation. The new simplified activation using the `[simple]` extra provides the same convenience but with explicit opt-in, making it clear when version inference should be enabled. ### Version files Version files can be created with the ``version_file`` directive. ```toml title="pyproject.toml" ... [tool.setuptools_scm] version_file = "pkg/_version.py" ``` Where ``pkg`` is the name of your package. Unless the small overhead of introspecting the version at runtime via `importlib.metadata` is a concern or you need a version file in an alternative format such as plain-text (see ``version_file_template``) you most likely do _not_ need to write a separate version file; see the runtime discussion below for more details. ## As cli tool If you need to confirm which version string is being generated or debug the configuration, you can install [setuptools-scm](https://github.com/pypa/setuptools-scm) directly in your working environment and run: ```commandline $ python -m setuptools_scm # example from running local after changes 7.1.1.dev149+g5197d0f.d20230727 ``` and to list all tracked by the scm: ```commandline $ python -m setuptools_scm ls # output trimmed for brevity ./LICENSE ... ./src/setuptools_scm/__init__.py ./src/... ... ``` !!! note "Committed files only" currently only committed files are listed, this might change in the future !!! warning "sdists/archives don't provide file lists" Currently there is no builtin mechanism to safely transfer the file lists to sdists or obtaining them from archives. Coordination for setuptools and hatch is ongoing. To explore other options, try ```commandline $ python -m setuptools_scm --help ``` ## At runtime ### Python Metadata The standard method to retrieve the version number at runtime is via [PEP-0566](https://www.python.org/dev/peps/pep-0566/) metadata using ``importlib.metadata`` from the standard library (added in Python 3.8) or the [`importlib_metadata`](https://pypi.org/project/importlib-metadata/) backport for earlier versions: ```python title="package_name/__init__.py" from importlib.metadata import version, PackageNotFoundError try: __version__ = version("package-name") except PackageNotFoundError: # package is not installed pass ``` ### Via your version file If you have opted to create a Python version file via the standard template, you can import that file, where you will have a ``version`` string and a ``version_tuple`` tuple with elements corresponding to the version tags. ```python title="Using package_name/_version.py" import package_name._version as v print(v.version) print(v.version_tuple) ``` ### Via setuptools_scm (strongly discouraged) While the most simple **looking** way to use `setuptools_scm` at runtime is: ```python from setuptools_scm import get_version version = get_version() ``` it is strongly discouraged to call directly into `setuptools_scm` over the standard Python `importlib.metadata`. In order to use `setuptools_scm` from code that is one directory deeper than the project's root, you can use: ```python from setuptools_scm import get_version version = get_version(root='..', relative_to=__file__) ``` For legacy configurations or when working with extracted archives (like PyPI tarballs), you may need to specify a `fallback_root` parameter. This is particularly useful for legacy Sphinx configurations that use `get_version()` instead of getting the version from the installed package: ```python from setuptools_scm import get_version # For legacy Sphinx conf.py that needs to work both in development and from archives version = get_version(root='..', fallback_root='..', relative_to=__file__) ``` The `fallback_root` parameter specifies the directory to use when the SCM metadata is not available (e.g., in extracted tarballs), while `root` is used when SCM metadata is present. ### Usage from Sphinx The recommended approach for Sphinx configurations is to use the installed package metadata: ``` {.python file=docs/.entangled/sphinx_conf.py} from importlib.metadata import version as get_version release: str = get_version("package-name") # for example take major/minor version: str = ".".join(release.split('.')[:2]) ``` The underlying reason is that services like *Read the Docs* sometimes change the working directory for good reasons and using the installed metadata prevents using needless volatile data there. !!! note "Legacy Sphinx configurations" If you have a legacy Sphinx configuration that still uses `setuptools_scm.get_version()` directly (instead of `importlib.metadata`), you may need to use the `fallback_root` parameter to ensure it works both in development and when building from archives: ```python from setuptools_scm import get_version # Legacy approach - use fallback_root for archive compatibility release = get_version(root='..', fallback_root='..', relative_to=__file__) version = ".".join(release.split('.')[:2]) ``` However, it's strongly recommended to migrate to the `importlib.metadata` approach above. ### With Docker/Podman In some situations, Docker may not copy the `.git` into the container when building images. Because of this, builds with version inference may fail. The following snippet exposes the external `.git` directory without copying. This allows the version to be inferred properly form inside the container without copying the entire `.git` folder into the container image. ```dockerfile RUN --mount=source=.git,target=.git,type=bind \ pip install --no-cache-dir -e . ``` However, this build step introduces a dependency to the state of your local `.git` folder the build cache and triggers the long-running pip install process on every build. To optimize build caching, one can use an environment variable to pretend a pseudo version that is used to cache the results of the pip install process: ```dockerfile FROM python COPY pyproject.toml ARG PSEUDO_VERSION=1 # strongly recommended to update based on git describe RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MY_PACKAGE=${PSEUDO_VERSION} pip install -e .[test] RUN --mount=source=.git,target=.git,type=bind pip install -e . ``` Note that running this Dockerfile requires docker with BuildKit enabled [docs](https://github.com/moby/buildkit/blob/v0.8.3/frontend/dockerfile/docs/syntax.md). To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired version as a build argument. Note that `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}` is preferred over `SETUPTOOLS_SCM_PRETEND_VERSION`. ## Default versioning scheme In the standard configuration `setuptools-scm` takes a look at three things: 1. latest tag (with a version number) 2. the distance to this tag (e.g. number of revisions since latest tag) 3. workdir state (e.g. uncommitted changes since latest tag) and uses roughly the following logic to render the version: | distance | state | format | |----------|-----------|----------------------------------------------------------------------| | no | unchanged | `{tag}` | | yes | unchanged | `{next_version}.dev{distance}+{scm letter}{revision hash}` | | no | changed | `{tag}+dYYYYMMDD` | | yes | changed | `{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD` | where `{next_version}` is the next version number after the latest tag The next version is calculated by adding `1` to the last numeric component of the tag. For Git projects, the version relies on [git describe](https://git-scm.com/docs/git-describe), so you will see an additional `g` prepended to the `{revision hash}`. ## Version Tag Formats setuptools-scm automatically detects version information from SCM tags. The default tag regex supports a wide variety of tag formats, with the **"v" prefix being recommended** for clarity and consistency. ### Recommended Tag Format **Use the "v" prefix for version tags:** ```bash git tag v1.0.0 # Recommended git tag v2.1.3 git tag v1.0.0-alpha1 git tag v1.0.0-rc1 ``` ### Supported Tag Formats setuptools-scm's default tag regex supports: - **Version prefix**: `v` or `V` (optional, but recommended) - **Project prefix**: Optional project name followed by dashes (e.g., `myproject-v1.0.0`) - **Version number**: Standard semantic versioning patterns - **Pre-release suffixes**: Alpha, beta, RC versions - **Build metadata**: Anything after `+` is ignored **Examples of valid tags:** ```bash # Recommended formats (with v prefix) v1.0.0 v2.1.3 v1.0.0-alpha1 v1.0.0-beta2 v1.0.0-rc1 v1.2.3-dev V1.0.0 # Capital V also works # Project-prefixed formats myproject-v1.0.0 my-lib-v2.1.0 # Without v prefix (supported but not recommended) 1.0.0 2.1.3 1.0.0-alpha1 # With build metadata (metadata after + is ignored) v1.0.0+build.123 v1.0.0+20240115 ``` ### Why Use the "v" Prefix? 1. **Clarity**: Makes it immediately obvious that the tag represents a version 2. **Convention**: Widely adopted standard across the software industry 3. **Git compatibility**: Works well with git's tag sorting and filtering 4. **Tool compatibility**: Many other tools expect version tags to have a "v" prefix ### Custom Tag Patterns If you need different tag patterns, you can customize the tag regex: ```toml title="pyproject.toml" [tool.setuptools_scm] tag_regex = "^release-(?P[0-9]+\\.[0-9]+\\.[0-9]+)$" ``` ## Node ID Prefixes setuptools-scm automatically prepends identifying characters to node IDs (commit/revision hashes) to distinguish between different SCM systems: - **Git repositories**: Node IDs are prefixed with `g` (e.g., `g1a2b3c4d5`) - **Mercurial repositories**: Node IDs are prefixed with `h` (e.g., `h1a2b3c4d5`) This prefixing serves several purposes: 1. **SCM identification**: Makes it clear which version control system was used 2. **Consistency**: Ensures predictable node ID format across different SCM backends 3. **Debugging**: Helps identify the source SCM when troubleshooting version issues The prefixes are automatically added by setuptools-scm and should be included when manually specifying node IDs in environment variables like `SETUPTOOLS_SCM_PRETEND_METADATA`. **Examples:** ```bash # Git node ID 1.0.0.dev5+g1a2b3c4d5 # Mercurial node ID 1.0.0.dev5+h1a2b3c4d5 ``` !!! note According to [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers>), if a version includes a local component, the package cannot be published to public package indexes like PyPI or TestPyPI. The disallowed version segments may be seen in auto-publishing workflows or when a configuration mistake is made. However, some package indexes such as devpi or other alternatives allow local versions. Local version identifiers must comply with [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers>). ## Semantic Versioning (SemVer) Due to the default behavior it's necessary to always include a patch version (the `3` in `1.2.3`), or else the automatic guessing will increment the wrong part of the SemVer (e.g. tag `2.0` results in `2.1.devX` instead of `2.0.1.devX`). So please make sure to tag accordingly. ## Builtin mechanisms for obtaining version numbers 1. the SCM itself (Git/Mercurial) 2. `.hg_archival` files (Mercurial archives) 3. `.git_archival.txt` files (Git archives, see subsection below) 4. `PKG-INFO` ### Git archives Git archives are supported, but require specific setup and understanding of how they work with package building. #### Overview When you create a `.git_archival.txt` file in your repository, it enables setuptools-scm to extract version information from git archives (e.g., GitHub's source downloads). However, this file contains template placeholders that must be expanded by `git archive` - they won't work when building directly from your working directory. #### Setting up git archival support You can generate a `.git_archival.txt` file using the setuptools-scm CLI: ```commandline # Generate a stable archival file (recommended for releases) $ python -m setuptools_scm create-archival-file --stable # Generate a full archival file with all metadata (use with caution) $ python -m setuptools_scm create-archival-file --full ``` Alternatively, you can create the file manually: **Stable version (recommended):** ```{ .text file=".git_archival.txt"} node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ ``` **Full version (with branch information - can cause instability):** ```{ .text file=".git_archival.txt"} # WARNING: Including ref-names can make archive checksums unstable # after commits are added post-release. Use only if describe-name is insufficient. node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ ref-names: $Format:%D$ ``` Feel free to alter the `match` field in `describe-name` to match your project's tagging style. !!! note If your git host provider does not properly expand `describe-name`, you may need to include `ref-names: $Format:%D$`. But **beware**, this can often lead to the git archive's checksum changing after a commit is added post-release. See [this issue][git-archive-issue] for more details. ``` {.text file=".gitattributes"} .git_archival.txt export-subst ``` Finally, commit both files: ```commandline $ git add .git_archival.txt .gitattributes && git commit -m "add git archive support" ``` #### Understanding the warnings If you see warnings like these when building your package: ``` UserWarning: git archive did not support describe output UserWarning: unprocessed git archival found (no export subst applied) ``` This typically happens when: 1. **Building from working directory**: You're running `python -m build` directly in your repository 2. **Sdist extraction**: A build tool extracts your sdist to build wheels, but the extracted directory isn't a git repository #### Recommended build workflows **For development builds:** Exclude `.git_archival.txt` from your package to avoid warnings: ```{ .text file="MANIFEST.in"} # Exclude archival file from development builds exclude .git_archival.txt ``` **For release builds from archives:** Build from an actual git archive to ensure proper template expansion: ```commandline # Create archive from a specific tag/commit $ git archive --output=../source_archive.tar v1.2.3 $ cd .. $ tar -xf source_archive.tar $ cd extracted_directory/ $ python -m build . ``` **For automated releases:** Many CI systems and package repositories (like GitHub Actions) automatically handle this correctly when building from git archives. #### Integration with package managers **MANIFEST.in exclusions:** ```{ .text file="MANIFEST.in"} # Exclude development files from packages exclude .git_archival.txt exclude .gitattributes ``` ```{ .text file=".gitattributes"} # Archive configuration .git_archival.txt export-subst .gitignore export-ignore ``` #### Troubleshooting **Problem: "unprocessed git archival found" warnings** - ✅ **Solution**: Add `exclude .git_archival.txt` to `MANIFEST.in` for development builds - ✅ **Alternative**: Build from actual git archives for releases **Problem: "git archive did not support describe output" warnings** - ℹ️ **Information**: This is expected when `.git_archival.txt` contains unexpanded templates - ✅ **Solution**: Same as above - exclude file or build from git archives **Problem: Version detection fails in git archives** - ✅ **Check**: Is `.gitattributes` configured with `export-subst`? - ✅ **Check**: Are you building from a properly created git archive? - ✅ **Check**: Does your git hosting provider support archive template expansion? !!! warning "Branch Names and Archive Stability" Including `ref-names: $Format:%D$` in your `.git_archival.txt` can make archive checksums change when new commits are added to branches referenced in the archive. This primarily affects GitHub's automatic source archives. Use the stable format (without `ref-names`) unless you specifically need branch information and understand the stability implications. !!! note "Version Files" If you are creating a `_version.py` file, it should not be kept in version control. Add it to `.gitignore`: ``` # Generated version file src/mypackage/_version.py ``` [git-archive-issue]: https://github.com/pypa/setuptools-scm/issues/806 ### File finders hook makes most of `MANIFEST.in` unnecessary !!! warning "Automatic File Inclusion" **`setuptools-scm` automatically provides a setuptools file finder by default.** This means that when you install setuptools-scm, it will automatically include **all SCM-tracked files** in your source distributions (sdist) without requiring a `MANIFEST.in` file. This automatic behavior can be surprising if you're not expecting it. The file finder is active as soon as setuptools-scm is installed in your build environment. `setuptools-scm` implements a [file_finders] entry point which returns all files tracked by your SCM. This eliminates the need for a manually constructed `MANIFEST.in` in most cases where this would be required when not using `setuptools-scm`. [file_finders]: https://setuptools.pypa.io/en/stable/userguide/extension.html #### How it works 1. **Automatic Discovery**: When building source distributions (`python -m build --sdist`), setuptools automatically calls the `setuptools-scm` file finder 2. **SCM Integration**: The file finder queries your SCM (Git/Mercurial) for all tracked files 3. **Inclusion**: All tracked files are automatically included in the sdist #### Controlling file inclusion **To exclude unwanted files:** 1. **Use `MANIFEST.in`** to exclude specific files/patterns: ``` exclude development.txt recursive-exclude tests *.pyc ``` 2. **Configure Git archive** (for Git repositories): ```bash # Add to .gitattributes tests/ export-ignore *.md export-ignore ``` 3. **Use `.hgignore`** or **Mercurial archive configuration** (for Mercurial repositories) #### Troubleshooting **Problem: Unwanted files in my package** - ✅ **Solution**: Add exclusions to `MANIFEST.in` - ✅ **Alternative**: Use Git/Mercurial archive configuration **Problem: Missing files in package** - ✅ **Check**: Are the files tracked in your SCM? - ✅ **Solution**: `git add` missing files or override with `MANIFEST.in` **Problem: File finder not working** - ✅ **Check**: Is setuptools-scm installed in your build environment? - ✅ **Check**: Are you in a valid SCM repository? ### Timestamps for Local Development Versions !!! info "Improved Timestamp Behavior" When your working directory has uncommitted changes (dirty), setuptools-scm now uses the **actual modification time of changed files** instead of the current time for local version schemes like `node-and-date`. **Before**: Dirty working directories always used current time (`now`) **Now**: Uses the latest modification time of changed files, falling back to current time only if no changed files are found This provides more stable and meaningful timestamps that reflect when you actually made changes to your code. **How it works:** 1. **Clean repository**: Uses commit timestamp from SCM 2. **Dirty repository**: Uses latest modification time of changed files 3. **Fallback**: Uses current time if no modification times can be determined **Benefits:** - More stable builds during development - Timestamps reflect actual change times - Better for reproducible development workflows setuptools-scm-9.2.2/hatch.toml000066400000000000000000000005331507525030000165070ustar00rootroot00000000000000[envs.test] extras = ["test", "dev"] [envs.test.scripts] all = "pytest {args}" [[env.test.matrix]] python = ["3.8", "3.9", "3.10", "3.11"] [envs.docs] python = "3.11" extras = ["docs"] [envs.docs.scripts] build = "mkdocs build --clean --strict" serve = "mkdocs serve --dev-addr localhost:8000" init = "mkdocs {args}" sync = ["entangled sync"]setuptools-scm-9.2.2/mkdocs.yml000066400000000000000000000012201507525030000165200ustar00rootroot00000000000000site_name: setuptools scm nav: - index.md - usage.md - customizing.md - config.md - integrations.md - extending.md - overrides.md - changelog.md theme: name: material watch: - src/setuptools_scm - docs markdown_extensions: - def_list - admonition - pymdownx.tasklist: custom_checkbox: true - pymdownx.superfences plugins: - entangled - search - include-markdown - mkdocstrings: default_handler: python handlers: python: paths: [ src ] options: separate_signature: true show_signature_annotations: true allow_inspection: true show_root_heading: true setuptools-scm-9.2.2/mypy.ini000066400000000000000000000002021507525030000162130ustar00rootroot00000000000000[mypy] python_version = 3.8 warn_return_any = True warn_unused_configs = True mypy_path = $MYPY_CONFIG_FILE_DIR/src strict = true setuptools-scm-9.2.2/nextgen/000077500000000000000000000000001507525030000161725ustar00rootroot00000000000000setuptools-scm-9.2.2/nextgen/vcs-versioning/000077500000000000000000000000001507525030000211465ustar00rootroot00000000000000setuptools-scm-9.2.2/nextgen/vcs-versioning/LICENSE.txt000066400000000000000000000020731507525030000227730ustar00rootroot00000000000000MIT License Copyright (c) 2023-present Ronny Pfannschmidt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. setuptools-scm-9.2.2/nextgen/vcs-versioning/README.md000066400000000000000000000010101507525030000224150ustar00rootroot00000000000000# vcs-versioning [![PyPI - Version](https://img.shields.io/pypi/v/vcs-versioning.svg)](https://pypi.org/project/vcs-versioning) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/vcs-versioning.svg)](https://pypi.org/project/vcs-versioning) ----- **Table of Contents** - [Installation](#installation) - [License](#license) ## Installation ```console pip install vcs-versioning ``` ## License `vcs-versioning` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. setuptools-scm-9.2.2/nextgen/vcs-versioning/pyproject.toml000066400000000000000000000031041507525030000240600ustar00rootroot00000000000000[build-system] build-backend = "hatchling.build" requires = [ "hatchling", ] [project] name = "vcs-versioning" description = "the blessed package to manage your versions by vcs metadata" readme = "README.md" keywords = [ ] license = "MIT" authors = [ { name = "Ronny Pfannschmidt", email = "opensource@ronnypfannschmidt.de" }, ] requires-python = ">=3.8" classifiers = [ "Development Status :: 1 - Planning", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] dynamic = [ "version", ] dependencies = [ ] [project.urls] Documentation = "https://github.com/unknown/vcs-versioning#readme" Issues = "https://github.com/unknown/vcs-versioning/issues" Source = "https://github.com/unknown/vcs-versioning" [tool.hatch.version] path = "vcs_versioning/__about__.py" [tool.hatch.envs.default] dependencies = [ "pytest", "pytest-cov", ] [tool.hatch.envs.default.scripts] cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=vcs_versioning --cov=tests {args}" no-cov = "cov --no-cov {args}" [[tool.hatch.envs.test.matrix]] python = [ "38", "39", "310", "311", "312", "313" ] [tool.coverage.run] branch = true parallel = true omit = [ "vcs_versioning/__about__.py", ] [tool.coverage.report] exclude_lines = [ "no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] setuptools-scm-9.2.2/nextgen/vcs-versioning/tests/000077500000000000000000000000001507525030000223105ustar00rootroot00000000000000setuptools-scm-9.2.2/nextgen/vcs-versioning/tests/__init__.py000066400000000000000000000000431507525030000244160ustar00rootroot00000000000000from __future__ import annotations setuptools-scm-9.2.2/nextgen/vcs-versioning/vcs_versioning/000077500000000000000000000000001507525030000242045ustar00rootroot00000000000000setuptools-scm-9.2.2/nextgen/vcs-versioning/vcs_versioning/__about__.py000066400000000000000000000000721507525030000264630ustar00rootroot00000000000000from __future__ import annotations __version__ = "0.0.1" setuptools-scm-9.2.2/nextgen/vcs-versioning/vcs_versioning/__init__.py000066400000000000000000000000431507525030000263120ustar00rootroot00000000000000from __future__ import annotations setuptools-scm-9.2.2/pyproject.toml000066400000000000000000000125541507525030000174450ustar00rootroot00000000000000 [build-system] build-backend = "_own_version_helper:build_meta" requires = [ "setuptools>=61", 'tomli<=2.0.2; python_version < "3.11"', ] backend-path = [ ".", "src", ] [project] name = "setuptools-scm" description = "the blessed package to manage your versions by scm tags" readme = "README.md" license.file = "LICENSE" authors = [ {name="Ronny Pfannschmidt", email="opensource@ronnypfannschmidt.de"} ] requires-python = ">=3.8" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Version Control", "Topic :: System :: Software Distribution", "Topic :: Utilities", ] dynamic = [ "version", ] dependencies = [ "packaging>=20", # https://github.com/pypa/setuptools-scm/issues/1112 - re-pin in a breaking release "setuptools", # >= 61", 'tomli>=1; python_version < "3.11"', 'typing-extensions; python_version < "3.10"', ] [project.optional-dependencies] rich = ["rich"] simple = [] toml = [] [dependency-groups] docs = [ #"entangled-cli~=2.0", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-include-markdown-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments", ] test = [ "pip", "build", "pytest", "pytest-timeout", # Timeout protection for CI/CD "rich", "ruff", "mypy~=1.13.0", # pinned to old for python 3.8 'typing-extensions; python_version < "3.11"', "wheel", "griffe", "flake8", ] [project.urls] documentation = "https://setuptools-scm.readthedocs.io/" repository = "https://github.com/pypa/setuptools-scm/" [project.entry-points.console_scripts] setuptools-scm = "setuptools_scm._cli:main" [project.entry-points."distutils.setup_keywords"] use_scm_version = "setuptools_scm._integration.setuptools:version_keyword" [project.entry-points."pipx.run"] setuptools-scm = "setuptools_scm._cli:main" setuptools_scm = "setuptools_scm._cli:main" [project.entry-points."setuptools.file_finders"] setuptools_scm = "setuptools_scm._file_finders:find_files" [project.entry-points."setuptools.finalize_distribution_options"] setuptools_scm = "setuptools_scm._integration.setuptools:infer_version" [project.entry-points."setuptools_scm.files_command"] ".git" = "setuptools_scm._file_finders.git:git_find_files" ".hg" = "setuptools_scm._file_finders.hg:hg_find_files" [project.entry-points."setuptools_scm.files_command_fallback"] ".git_archival.txt" = "setuptools_scm._file_finders.git:git_archive_find_files" ".hg_archival.txt" = "setuptools_scm._file_finders.hg:hg_archive_find_files" [project.entry-points."setuptools_scm.local_scheme"] dirty-tag = "setuptools_scm.version:get_local_dirty_tag" no-local-version = "setuptools_scm.version:get_no_local_node" node-and-date = "setuptools_scm.version:get_local_node_and_date" node-and-timestamp = "setuptools_scm.version:get_local_node_and_timestamp" [project.entry-points."setuptools_scm.parse_scm"] ".git" = "setuptools_scm.git:parse" ".hg" = "setuptools_scm.hg:parse" [project.entry-points."setuptools_scm.parse_scm_fallback"] ".git_archival.txt" = "setuptools_scm.git:parse_archival" ".hg_archival.txt" = "setuptools_scm.hg:parse_archival" PKG-INFO = "setuptools_scm.fallbacks:parse_pkginfo" "pyproject.toml" = "setuptools_scm.fallbacks:fallback_version" "setup.py" = "setuptools_scm.fallbacks:fallback_version" [project.entry-points."setuptools_scm.version_scheme"] "calver-by-date" = "setuptools_scm.version:calver_by_date" "guess-next-dev" = "setuptools_scm.version:guess_next_dev_version" "no-guess-dev" = "setuptools_scm.version:no_guess_dev_version" "only-version" = "setuptools_scm.version:only_version" "post-release" = "setuptools_scm.version:postrelease_version" "python-simplified-semver" = "setuptools_scm.version:simplified_semver_version" "release-branch-semver" = "setuptools_scm.version:release_branch_semver_version" [tool.setuptools.packages.find] where = ["src"] namespaces = false [tool.setuptools.dynamic] version = { attr = "_own_version_helper.version"} [tool.setuptools_scm] [tool.ruff] lint.extend-select = ["YTT", "B", "C4", "DTZ", "ISC", "LOG", "G", "PIE", "PYI", "PT", "FLY", "I", "C90", "PERF", "W", "PGH", "PLE", "UP", "FURB", "RUF"] lint.ignore = ["B028", "LOG015", "PERF203"] lint.preview = true [tool.ruff.lint.isort] force-single-line = true from-first = false lines-between-types = 1 order-by-type = true [tool.repo-review] ignore = ["PP305", "GH103", "GH212", "MY100", "PC111", "PC160", "PC170", "PC180", "PC901"] [tool.pytest.ini_options] minversion = "8" testpaths = ["testing"] timeout = 300 # 5 minutes timeout per test for CI protection filterwarnings = [ "error", "ignore:.*tool\\.setuptools_scm.*", "ignore:.*git archive did not support describe output.*:UserWarning", ] log_level = "debug" log_cli_level = "info" # disable unraisable until investigated addopts = ["-ra", "--strict-config", "--strict-markers"] markers = [ "issue(id): reference to github issue", "skip_commit: allows to skip committing in the helpers", ] [tool.uv] default-groups = ["test", "docs"] setuptools-scm-9.2.2/src/000077500000000000000000000000001507525030000153115ustar00rootroot00000000000000setuptools-scm-9.2.2/src/setuptools_scm/000077500000000000000000000000001507525030000203745ustar00rootroot00000000000000setuptools-scm-9.2.2/src/setuptools_scm/.git_archival.txt000066400000000000000000000002071507525030000236460ustar00rootroot00000000000000node: e56b78fcd4c1e79aa36c1e4700fca34c8ff540fe node-date: 2025-10-19T22:57:36+02:00 describe-name: %(describe:tags=true,match=*[0-9]*) setuptools-scm-9.2.2/src/setuptools_scm/__init__.py000066400000000000000000000014211507525030000225030ustar00rootroot00000000000000""" :copyright: 2010-2023 by Ronny Pfannschmidt :license: MIT """ from __future__ import annotations from ._config import DEFAULT_LOCAL_SCHEME from ._config import DEFAULT_VERSION_SCHEME from ._config import Configuration from ._get_version_impl import _get_version from ._get_version_impl import get_version from ._integration.dump_version import dump_version # soft deprecated from ._version_cls import NonNormalizedVersion from ._version_cls import Version from .version import ScmVersion # Public API __all__ = [ "DEFAULT_LOCAL_SCHEME", "DEFAULT_VERSION_SCHEME", "Configuration", "NonNormalizedVersion", "ScmVersion", "Version", "_get_version", "dump_version", # soft deprecated imports, left for backward compatibility "get_version", ] setuptools-scm-9.2.2/src/setuptools_scm/__main__.py000066400000000000000000000001641507525030000224670ustar00rootroot00000000000000from __future__ import annotations from ._cli import main if __name__ == "__main__": raise SystemExit(main()) setuptools-scm-9.2.2/src/setuptools_scm/_cli.py000066400000000000000000000223231507525030000216560ustar00rootroot00000000000000from __future__ import annotations import argparse import json import os import sys from pathlib import Path from typing import Any from setuptools_scm import Configuration from setuptools_scm._file_finders import find_files from setuptools_scm._get_version_impl import _get_version from setuptools_scm.discover import walk_potential_roots def main(args: list[str] | None = None) -> int: opts = _get_cli_opts(args) inferred_root: str = opts.root or "." pyproject = opts.config or _find_pyproject(inferred_root) try: config = Configuration.from_file( pyproject, root=(os.path.abspath(opts.root) if opts.root is not None else None), ) except (LookupError, FileNotFoundError) as ex: # no pyproject.toml OR no [tool.setuptools_scm] print( f"Warning: could not use {os.path.relpath(pyproject)}," " using default configuration.\n" f" Reason: {ex}.", file=sys.stderr, ) config = Configuration(root=inferred_root) version: str | None if opts.no_version: version = "0.0.0+no-version-was-requested.fake-version" else: version = _get_version( config, force_write_version_files=opts.force_write_version_files ) if version is None: raise SystemExit("ERROR: no version found for", opts) if opts.strip_dev: version = version.partition(".dev")[0] return command(opts, version, config) def _get_cli_opts(args: list[str] | None) -> argparse.Namespace: prog = "python -m setuptools_scm" desc = "Print project version according to SCM metadata" parser = argparse.ArgumentParser(prog, description=desc) # By default, help for `--help` starts with lower case, so we keep the pattern: parser.add_argument( "-r", "--root", default=None, help='directory managed by the SCM, default: inferred from config file, or "."', ) parser.add_argument( "-c", "--config", default=None, metavar="PATH", help="path to 'pyproject.toml' with setuptools-scm config, " "default: looked up in the current or parent directories", ) parser.add_argument( "--strip-dev", action="store_true", help="remove the dev/local parts of the version before printing the version", ) parser.add_argument( "-N", "--no-version", action="store_true", help="do not include package version in the output", ) output_formats = ["json", "plain", "key-value"] parser.add_argument( "-f", "--format", type=str.casefold, default="plain", help="specify output format", choices=output_formats, ) parser.add_argument( "-q", "--query", type=str.casefold, nargs="*", help="display setuptools-scm settings according to query, " "e.g. dist_name, do not supply an argument in order to " "print a list of valid queries.", ) parser.add_argument( "--force-write-version-files", action="store_true", help="trigger to write the content of the version files\n" "its recommended to use normal/editable installation instead)", ) sub = parser.add_subparsers(title="extra commands", dest="command", metavar="") # We avoid `metavar` to prevent printing repetitive information desc = "List information about the package, e.g. included files" sub.add_parser("ls", help=desc[0].lower() + desc[1:], description=desc) # Add create-archival-file subcommand archival_desc = "Create .git_archival.txt file for git archive support" archival_parser = sub.add_parser( "create-archival-file", help=archival_desc[0].lower() + archival_desc[1:], description=archival_desc, ) archival_group = archival_parser.add_mutually_exclusive_group(required=True) archival_group.add_argument( "--stable", action="store_true", help="create stable archival file (recommended, no branch names)", ) archival_group.add_argument( "--full", action="store_true", help="create full archival file with branch information (can cause instability)", ) archival_parser.add_argument( "--force", action="store_true", help="overwrite existing .git_archival.txt file" ) return parser.parse_args(args) # flake8: noqa: C901 def command(opts: argparse.Namespace, version: str, config: Configuration) -> int: data: dict[str, Any] = {} if opts.command == "ls": opts.query = ["files"] if opts.command == "create-archival-file": return _create_archival_file(opts, config) if opts.query == []: opts.no_version = True sys.stderr.write("Available queries:\n\n") opts.query = ["queries"] data["queries"] = ["files", *config.__dataclass_fields__] if opts.query is None: opts.query = [] if not opts.no_version: data["version"] = version if "files" in opts.query: data["files"] = find_files(config.root) for q in opts.query: if q in ["files", "queries", "version"]: continue try: if q.startswith("_"): raise AttributeError() data[q] = getattr(config, q) except AttributeError: sys.stderr.write(f"Error: unknown query: '{q}'\n") return 1 if opts.format == "json": print(json.dumps(data, indent=2)) if opts.format == "plain": _print_plain(data) if opts.format == "key-value": _print_key_value(data) return 0 def _print_plain(data: dict[str, Any]) -> None: version = data.pop("version", None) if version: print(version) files = data.pop("files", []) for file_ in files: print(file_) queries = data.pop("queries", []) for query in queries: print(query) if data: print("\n".join(data.values())) def _print_key_value(data: dict[str, Any]) -> None: for key, value in data.items(): if isinstance(value, str): print(f"{key} = {value}") else: str_value = "\n ".join(value) print(f"{key} = {str_value}") def _find_pyproject(parent: str) -> str: for directory in walk_potential_roots(os.path.abspath(parent)): pyproject = os.path.join(directory, "pyproject.toml") if os.path.isfile(pyproject): return pyproject return os.path.abspath( "pyproject.toml" ) # use default name to trigger the default errors def _create_archival_file(opts: argparse.Namespace, config: Configuration) -> int: """Create .git_archival.txt file with appropriate content.""" archival_path = Path(config.root, ".git_archival.txt") # Check if file exists and force flag if archival_path.exists() and not opts.force: print( f"Error: {archival_path} already exists. Use --force to overwrite.", file=sys.stderr, ) return 1 if opts.stable: content = _get_stable_archival_content() print("Creating stable .git_archival.txt (recommended for releases)") elif opts.full: content = _get_full_archival_content() print("Creating full .git_archival.txt with branch information") print("WARNING: This can cause archive checksums to be unstable!") try: archival_path.write_text(content, encoding="utf-8") print(f"Created: {archival_path}") gitattributes_path = Path(config.root, ".gitattributes") needs_gitattributes = True if gitattributes_path.exists(): # TODO: more nuanced check later gitattributes_content = gitattributes_path.read_text("utf-8") if ( ".git_archival.txt" in gitattributes_content and "export-subst" in gitattributes_content ): needs_gitattributes = False if needs_gitattributes: print("\nNext steps:") print("1. Add this line to .gitattributes:") print(" .git_archival.txt export-subst") print("2. Commit both files:") print(" git add .git_archival.txt .gitattributes") print(" git commit -m 'add git archive support'") else: print("\nNext step:") print("Commit the archival file:") print(" git add .git_archival.txt") print(" git commit -m 'update git archival file'") return 0 except OSError as e: print(f"Error: Could not create {archival_path}: {e}", file=sys.stderr) return 1 def _get_stable_archival_content() -> str: """Generate stable archival file content (no branch names).""" return """\ node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ """ def _get_full_archival_content() -> str: """Generate full archival file content with branch information.""" return """\ # WARNING: Including ref-names can make archive checksums unstable # after commits are added post-release. Use only if describe-name is insufficient. node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ ref-names: $Format:%D$ """ setuptools-scm-9.2.2/src/setuptools_scm/_compat.py000066400000000000000000000042211507525030000223670ustar00rootroot00000000000000"""Compatibility utilities for cross-platform functionality.""" from __future__ import annotations def normalize_path_for_assertion(path: str) -> str: """Normalize path separators for cross-platform assertions. On Windows, this converts backslashes to forward slashes to ensure path comparisons work correctly. On other platforms, returns the path unchanged. The length of the string is not changed by this operation. Args: path: The path string to normalize Returns: The path with normalized separators """ return path.replace("\\", "/") def strip_path_suffix( full_path: str, suffix_path: str, error_msg: str | None = None ) -> str: """Strip a suffix from a path, with cross-platform path separator handling. This function first normalizes path separators for Windows compatibility, then asserts that the full path ends with the suffix, and finally returns the path with the suffix removed. This is the common pattern used for computing parent directories from git output. Args: full_path: The full path string suffix_path: The suffix path to strip from the end error_msg: Optional custom error message for the assertion Returns: The prefix path with the suffix removed Raises: AssertionError: If the full path doesn't end with the suffix """ normalized_full = normalize_path_for_assertion(full_path) if error_msg: assert normalized_full.endswith(suffix_path), error_msg else: assert normalized_full.endswith(suffix_path), ( f"Path assertion failed: {full_path!r} does not end with {suffix_path!r}" ) return full_path[: -len(suffix_path)] # Legacy aliases for backward compatibility during transition def assert_path_endswith( full_path: str, suffix_path: str, error_msg: str | None = None ) -> None: """Legacy alias - use strip_path_suffix instead.""" strip_path_suffix(full_path, suffix_path, error_msg) def compute_path_prefix(full_path: str, suffix_path: str) -> str: """Legacy alias - use strip_path_suffix instead.""" return strip_path_suffix(full_path, suffix_path) setuptools-scm-9.2.2/src/setuptools_scm/_config.py000066400000000000000000000250211507525030000223520ustar00rootroot00000000000000"""configuration""" from __future__ import annotations import dataclasses import os import re import warnings from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import Pattern from typing import Protocol if TYPE_CHECKING: from . import git from . import _log from . import _types as _t from ._integration.pyproject_reading import PyProjectData from ._integration.pyproject_reading import ( get_args_for_pyproject as _get_args_for_pyproject, ) from ._integration.pyproject_reading import read_pyproject as _read_pyproject from ._overrides import read_toml_overrides from ._version_cls import Version as _Version from ._version_cls import _validate_version_cls from ._version_cls import _VersionT log = _log.log.getChild("config") def _is_called_from_dataclasses() -> bool: """Check if the current call is from the dataclasses module.""" import inspect frame = inspect.currentframe() try: # Walk up to 7 frames to check for dataclasses calls current_frame = frame assert current_frame is not None for _ in range(7): current_frame = current_frame.f_back if current_frame is None: break if "dataclasses.py" in current_frame.f_code.co_filename: return True return False finally: del frame class _GitDescribeCommandDescriptor: """Data descriptor for deprecated git_describe_command field.""" def __get__( self, obj: Configuration | None, objtype: type[Configuration] | None = None ) -> _t.CMD_TYPE | None: if obj is None: return self # type: ignore[return-value] # Only warn if not being called by dataclasses.replace or similar introspection is_from_dataclasses = _is_called_from_dataclasses() if not is_from_dataclasses: warnings.warn( "Configuration field 'git_describe_command' is deprecated. " "Use 'scm.git.describe_command' instead.", DeprecationWarning, stacklevel=2, ) return obj.scm.git.describe_command def __set__(self, obj: Configuration, value: _t.CMD_TYPE | None) -> None: warnings.warn( "Configuration field 'git_describe_command' is deprecated. " "Use 'scm.git.describe_command' instead.", DeprecationWarning, stacklevel=2, ) obj.scm.git.describe_command = value DEFAULT_TAG_REGEX = re.compile( r"^(?:[\w-]+-)?(?P[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$" ) """default tag regex that tries to match PEP440 style versions with prefix consisting of dashed words""" DEFAULT_VERSION_SCHEME = "guess-next-dev" DEFAULT_LOCAL_SCHEME = "node-and-date" def _check_tag_regex(value: str | Pattern[str] | None) -> Pattern[str]: if not value: regex = DEFAULT_TAG_REGEX else: regex = re.compile(value) group_names = regex.groupindex.keys() if regex.groups == 0 or (regex.groups > 1 and "version" not in group_names): raise ValueError( f"Expected tag_regex '{regex.pattern}' to contain a single match group or" " a group named 'version' to identify the version part of any tag." ) return regex def _get_default_git_pre_parse() -> git.GitPreParse: """Get the default git pre_parse enum value""" from . import git return git.GitPreParse.WARN_ON_SHALLOW class ParseFunction(Protocol): def __call__( self, root: _t.PathT, *, config: Configuration ) -> _t.SCMVERSION | None: ... def _check_absolute_root(root: _t.PathT, relative_to: _t.PathT | None) -> str: log.debug("check absolute root=%s relative_to=%s", root, relative_to) if relative_to: if ( os.path.isabs(root) and os.path.isabs(relative_to) and not os.path.commonpath([root, relative_to]) == root ): warnings.warn( f"absolute root path '{root}' overrides relative_to '{relative_to}'" ) if os.path.isdir(relative_to): warnings.warn( "relative_to is expected to be a file," f" its the directory {relative_to}\n" "assuming the parent directory was passed" ) log.debug("dir %s", relative_to) root = os.path.join(relative_to, root) else: log.debug("file %s", relative_to) root = os.path.join(os.path.dirname(relative_to), root) return os.path.abspath(root) @dataclasses.dataclass class GitConfiguration: """Git-specific configuration options""" pre_parse: git.GitPreParse = dataclasses.field( default_factory=lambda: _get_default_git_pre_parse() ) describe_command: _t.CMD_TYPE | None = None @classmethod def from_data(cls, data: dict[str, Any]) -> GitConfiguration: """Create GitConfiguration from configuration data, converting strings to enums""" git_data = data.copy() # Convert string pre_parse values to enum instances if "pre_parse" in git_data and isinstance(git_data["pre_parse"], str): from . import git try: git_data["pre_parse"] = git.GitPreParse(git_data["pre_parse"]) except ValueError as e: valid_options = [option.value for option in git.GitPreParse] raise ValueError( f"Invalid git pre_parse function '{git_data['pre_parse']}'. " f"Valid options are: {', '.join(valid_options)}" ) from e return cls(**git_data) @dataclasses.dataclass class ScmConfiguration: """SCM-specific configuration options""" git: GitConfiguration = dataclasses.field(default_factory=GitConfiguration) @classmethod def from_data(cls, data: dict[str, Any]) -> ScmConfiguration: """Create ScmConfiguration from configuration data""" scm_data = data.copy() # Handle git-specific configuration git_data = scm_data.pop("git", {}) git_config = GitConfiguration.from_data(git_data) return cls(git=git_config, **scm_data) @dataclasses.dataclass class Configuration: """Global configuration model""" relative_to: _t.PathT | None = None root: _t.PathT = "." version_scheme: _t.VERSION_SCHEME = DEFAULT_VERSION_SCHEME local_scheme: _t.VERSION_SCHEME = DEFAULT_LOCAL_SCHEME tag_regex: Pattern[str] = DEFAULT_TAG_REGEX parentdir_prefix_version: str | None = None fallback_version: str | None = None fallback_root: _t.PathT = "." write_to: _t.PathT | None = None write_to_template: str | None = None version_file: _t.PathT | None = None version_file_template: str | None = None parse: ParseFunction | None = None git_describe_command: dataclasses.InitVar[_t.CMD_TYPE | None] = ( _GitDescribeCommandDescriptor() ) dist_name: str | None = None version_cls: type[_VersionT] = _Version search_parent_directories: bool = False parent: _t.PathT | None = None # Nested SCM configurations scm: ScmConfiguration = dataclasses.field( default_factory=lambda: ScmConfiguration() ) # Deprecated fields (handled in __post_init__) def __post_init__(self, git_describe_command: _t.CMD_TYPE | None) -> None: self.tag_regex = _check_tag_regex(self.tag_regex) # Handle deprecated git_describe_command # Check if it's a descriptor object (happens when no value is passed) if git_describe_command is not None and not isinstance( git_describe_command, _GitDescribeCommandDescriptor ): # Check if this is being called from dataclasses is_from_dataclasses = _is_called_from_dataclasses() same_value = ( self.scm.git.describe_command is not None and self.scm.git.describe_command == git_describe_command ) if is_from_dataclasses and same_value: # Ignore the passed value - it's from dataclasses.replace() with same value pass else: warnings.warn( "Configuration field 'git_describe_command' is deprecated. " "Use 'scm.git.describe_command' instead.", DeprecationWarning, stacklevel=2, ) # Check for conflicts if self.scm.git.describe_command is not None: raise ValueError( "Cannot specify both 'git_describe_command' (deprecated) and " "'scm.git.describe_command'. Please use only 'scm.git.describe_command'." ) self.scm.git.describe_command = git_describe_command @property def absolute_root(self) -> str: return _check_absolute_root(self.root, self.relative_to) @classmethod def from_file( cls, name: str | os.PathLike[str] = "pyproject.toml", dist_name: str | None = None, pyproject_data: PyProjectData | None = None, **kwargs: Any, ) -> Configuration: """ Read Configuration from pyproject.toml (or similar). Raises exceptions when file is not found or toml is not installed or the file has invalid format. Parameters: - name: path to pyproject.toml - dist_name: name of the distribution - **kwargs: additional keyword arguments to pass to the Configuration constructor """ if pyproject_data is None: pyproject_data = _read_pyproject(Path(name)) args = _get_args_for_pyproject(pyproject_data, dist_name, kwargs) args.update(read_toml_overrides(args["dist_name"])) relative_to = args.pop("relative_to", name) return cls.from_data(relative_to=relative_to, data=args) @classmethod def from_data( cls, relative_to: str | os.PathLike[str], data: dict[str, Any] ) -> Configuration: """ given configuration data create a config instance after validating tag regex/version class """ version_cls = _validate_version_cls( data.pop("version_cls", None), data.pop("normalize", True) ) # Handle nested SCM configuration scm_data = data.pop("scm", {}) # Handle nested SCM configuration scm_config = ScmConfiguration.from_data(scm_data) return cls( relative_to=relative_to, version_cls=version_cls, scm=scm_config, **data, ) setuptools-scm-9.2.2/src/setuptools_scm/_entrypoints.py000066400000000000000000000071611507525030000235100ustar00rootroot00000000000000from __future__ import annotations import sys from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import Iterator from typing import cast from . import _log from . import version __all__ = [ "entry_points", "im", ] if TYPE_CHECKING: from . import _types as _t from ._config import Configuration from ._config import ParseFunction from importlib import metadata as im log = _log.log.getChild("entrypoints") if sys.version_info[:2] < (3, 10): def entry_points(*, group: str, name: str | None = None) -> list[im.EntryPoint]: # Python 3.9: entry_points() returns dict, need to handle filtering manually eps = im.entry_points() # Returns dict group_eps = eps.get(group, []) if name is not None: return [ep for ep in group_eps if ep.name == name] return group_eps else: def entry_points(*, group: str, name: str | None = None) -> im.EntryPoints: kw = {"group": group} if name is not None: kw["name"] = name return im.entry_points(**kw) def version_from_entrypoint( config: Configuration, *, entrypoint: str, root: _t.PathT ) -> version.ScmVersion | None: from .discover import iter_matching_entrypoints log.debug("version_from_ep %s in %s", entrypoint, root) for ep in iter_matching_entrypoints(root, entrypoint, config): fn: ParseFunction = ep.load() maybe_version: version.ScmVersion | None = fn(root, config=config) log.debug("%s found %r", ep, maybe_version) if maybe_version is not None: return maybe_version return None def _get_ep(group: str, name: str) -> Any | None: for ep in entry_points(group=group, name=name): log.debug("ep found: %s", ep.name) return ep.load() return None def _get_from_object_reference_str(path: str, group: str) -> Any | None: # todo: remove for importlib native spelling from importlib.metadata import EntryPoint # hack ep = EntryPoint(path, path, group) try: return ep.load() except (AttributeError, ModuleNotFoundError): return None def _iter_version_schemes( entrypoint: str, scheme_value: _t.VERSION_SCHEMES, _memo: set[object] | None = None, ) -> Iterator[Callable[[version.ScmVersion], str]]: if _memo is None: _memo = set() if isinstance(scheme_value, str): scheme_value = cast( "_t.VERSION_SCHEMES", _get_ep(entrypoint, scheme_value) or _get_from_object_reference_str(scheme_value, entrypoint), ) if isinstance(scheme_value, (list, tuple)): for variant in scheme_value: if variant not in _memo: _memo.add(variant) yield from _iter_version_schemes(entrypoint, variant, _memo=_memo) elif callable(scheme_value): yield scheme_value def _call_version_scheme( version: version.ScmVersion, entrypoint: str, given_value: _t.VERSION_SCHEMES, default: str | None = None, ) -> str: found_any_implementation = False for scheme in _iter_version_schemes(entrypoint, given_value): found_any_implementation = True result = scheme(version) if result is not None: return result if not found_any_implementation: raise ValueError( f'Couldn\'t find any implementations for entrypoint "{entrypoint}"' f' with value "{given_value}".' ) if default is not None: return default raise ValueError( f'None of the "{entrypoint}" entrypoints matching "{given_value}"' " returned a value." ) setuptools-scm-9.2.2/src/setuptools_scm/_file_finders/000077500000000000000000000000001507525030000231645ustar00rootroot00000000000000setuptools-scm-9.2.2/src/setuptools_scm/_file_finders/__init__.py000066400000000000000000000072241507525030000253020ustar00rootroot00000000000000from __future__ import annotations import os from typing import TYPE_CHECKING from typing import Callable from .. import _log from .. import _types as _t from .._entrypoints import entry_points from .pathtools import norm_real if TYPE_CHECKING: import sys if sys.version_info >= (3, 10): from typing import TypeGuard else: from typing_extensions import TypeGuard log = _log.log.getChild("file_finder") def scm_find_files( path: _t.PathT, scm_files: set[str], scm_dirs: set[str], force_all_files: bool = False, ) -> list[str]: """ setuptools compatible file finder that follows symlinks - path: the root directory from which to search - scm_files: set of scm controlled files and symlinks (including symlinks to directories) - scm_dirs: set of scm controlled directories (including directories containing no scm controlled files) - force_all_files: ignore ``scm_files`` and ``scm_dirs`` and list everything. scm_files and scm_dirs must be absolute with symlinks resolved (realpath), with normalized case (normcase) Spec here: https://setuptools.pypa.io/en/latest/userguide/extension.html#\ adding-support-for-revision-control-systems """ realpath = norm_real(path) seen: set[str] = set() res: list[str] = [] for dirpath, dirnames, filenames in os.walk(realpath, followlinks=True): # dirpath with symlinks resolved realdirpath = norm_real(dirpath) def _link_not_in_scm(n: str, realdirpath: str = realdirpath) -> bool: fn = os.path.join(realdirpath, os.path.normcase(n)) return os.path.islink(fn) and fn not in scm_files if not force_all_files and realdirpath not in scm_dirs: # directory not in scm, don't walk it's content dirnames[:] = [] continue if os.path.islink(dirpath) and not os.path.relpath( realdirpath, realpath ).startswith(os.pardir): # a symlink to a directory not outside path: # we keep it in the result and don't walk its content res.append(os.path.join(path, os.path.relpath(dirpath, path))) dirnames[:] = [] continue if realdirpath in seen: # symlink loop protection dirnames[:] = [] continue dirnames[:] = [ dn for dn in dirnames if force_all_files or not _link_not_in_scm(dn) ] for filename in filenames: if not force_all_files and _link_not_in_scm(filename): continue # dirpath + filename with symlinks preserved fullfilename = os.path.join(dirpath, filename) is_tracked = norm_real(fullfilename) in scm_files if force_all_files or is_tracked: res.append(os.path.join(path, os.path.relpath(fullfilename, realpath))) seen.add(realdirpath) return res def is_toplevel_acceptable(toplevel: str | None) -> TypeGuard[str]: """ """ if toplevel is None: return False ignored: list[str] = os.environ.get("SETUPTOOLS_SCM_IGNORE_VCS_ROOTS", "").split( os.pathsep ) ignored = [os.path.normcase(p) for p in ignored] log.debug("toplevel: %r\n ignored %s", toplevel, ignored) return toplevel not in ignored def find_files(path: _t.PathT = "") -> list[str]: eps = [ *entry_points(group="setuptools_scm.files_command"), *entry_points(group="setuptools_scm.files_command_fallback"), ] for ep in eps: command: Callable[[_t.PathT], list[str]] = ep.load() res: list[str] = command(path) if res: return res return [] setuptools-scm-9.2.2/src/setuptools_scm/_file_finders/git.py000066400000000000000000000105221507525030000243210ustar00rootroot00000000000000from __future__ import annotations import logging import os import subprocess import tarfile from typing import IO from .. import _types as _t from .._run_cmd import run as _run from ..integration import data_from_mime from . import is_toplevel_acceptable from . import scm_find_files from .pathtools import norm_real log = logging.getLogger(__name__) def _git_toplevel(path: str) -> str | None: try: cwd = os.path.abspath(path or ".") res = _run(["git", "rev-parse", "HEAD"], cwd=cwd) if res.returncode: # BAIL if there is no commit log.error("listing git files failed - pretending there aren't any") return None res = _run( ["git", "rev-parse", "--show-prefix"], cwd=cwd, ) if res.returncode: return None out = res.stdout[:-1] # remove the trailing pathsep if not out: out = cwd else: # Here, ``out`` is a relative path to root of git. # ``cwd`` is absolute path to current working directory. # the below method removes the length of ``out`` from # ``cwd``, which gives the git toplevel from .._compat import strip_path_suffix out = strip_path_suffix(cwd, out, f"cwd={cwd!r}\nout={out!r}") log.debug("find files toplevel %s", out) return norm_real(out) except subprocess.CalledProcessError: # git returned error, we are not in a git repo return None except OSError: # git command not found, probably return None def _git_interpret_archive(fd: IO[bytes], toplevel: str) -> tuple[set[str], set[str]]: with tarfile.open(fileobj=fd, mode="r|*") as tf: git_files = set() git_dirs = {toplevel} for member in tf.getmembers(): name = os.path.normcase(member.name).replace("/", os.path.sep) if member.type == tarfile.DIRTYPE: git_dirs.add(name) else: git_files.add(name) return git_files, git_dirs def _git_ls_files_and_dirs(toplevel: str) -> tuple[set[str], set[str]]: # use git archive instead of git ls-file to honor # export-ignore git attribute cmd = ["git", "archive", "--prefix", toplevel + os.path.sep, "HEAD"] log.info("running %s", " ".join(str(x) for x in cmd)) proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, cwd=toplevel, stderr=subprocess.DEVNULL ) assert proc.stdout is not None try: try: return _git_interpret_archive(proc.stdout, toplevel) finally: # ensure we avoid resource warnings by cleaning up the process proc.stdout.close() proc.terminate() # Wait for process to actually terminate and be reaped try: proc.wait(timeout=5) # Add timeout to avoid hanging except subprocess.TimeoutExpired: log.warning("git archive process did not terminate gracefully, killing") proc.kill() proc.wait() except Exception: # proc.wait() already called in finally block, check if it failed if proc.returncode != 0: log.error("listing git files failed - pretending there aren't any") return set(), set() def git_find_files(path: _t.PathT = "") -> list[str]: toplevel = _git_toplevel(os.fspath(path)) if not is_toplevel_acceptable(toplevel): return [] fullpath = norm_real(path) if not fullpath.startswith(toplevel): log.warning("toplevel mismatch computed %s vs resolved %s ", toplevel, fullpath) git_files, git_dirs = _git_ls_files_and_dirs(toplevel) return scm_find_files(path, git_files, git_dirs) def git_archive_find_files(path: _t.PathT = "") -> list[str]: # This function assumes that ``path`` is obtained from a git archive # and therefore all the files that should be ignored were already removed. archival = os.path.join(path, ".git_archival.txt") if not os.path.exists(archival): return [] data = data_from_mime(archival) if "$Format" in data.get("node", ""): # Substitutions have not been performed, so not a reliable archive return [] log.warning("git archive detected - fallback to listing all files") return scm_find_files(path, set(), set(), force_all_files=True) setuptools-scm-9.2.2/src/setuptools_scm/_file_finders/hg.py000066400000000000000000000042631507525030000241410ustar00rootroot00000000000000from __future__ import annotations import logging import os import subprocess from .. import _types as _t from .._file_finders import is_toplevel_acceptable from .._file_finders import scm_find_files from ..hg import run_hg from ..integration import data_from_mime from .pathtools import norm_real log = logging.getLogger(__name__) def _hg_toplevel(path: str) -> str | None: try: return run_hg( ["root"], cwd=(path or "."), check=True, ).parse_success(norm_real) except subprocess.CalledProcessError: # hg returned error, we are not in a mercurial repo return None except OSError: # hg command not found, probably return None def _hg_ls_files_and_dirs(toplevel: str) -> tuple[set[str], set[str]]: hg_files: set[str] = set() hg_dirs = {toplevel} res = run_hg(["files"], cwd=toplevel) if res.returncode: return set(), set() for name in res.stdout.splitlines(): name = os.path.normcase(name).replace("/", os.path.sep) fullname = os.path.join(toplevel, name) hg_files.add(fullname) dirname = os.path.dirname(fullname) while len(dirname) > len(toplevel) and dirname not in hg_dirs: hg_dirs.add(dirname) dirname = os.path.dirname(dirname) return hg_files, hg_dirs def hg_find_files(path: str = "") -> list[str]: toplevel = _hg_toplevel(path) if not is_toplevel_acceptable(toplevel): return [] assert toplevel is not None hg_files, hg_dirs = _hg_ls_files_and_dirs(toplevel) return scm_find_files(path, hg_files, hg_dirs) def hg_archive_find_files(path: _t.PathT = "") -> list[str]: # This function assumes that ``path`` is obtained from a mercurial archive # and therefore all the files that should be ignored were already removed. archival = os.path.join(path, ".hg_archival.txt") if not os.path.exists(archival): return [] data = data_from_mime(archival) if "node" not in data: # Ensure file is valid return [] log.warning("hg archive detected - fallback to listing all files") return scm_find_files(path, set(), set(), force_all_files=True) setuptools-scm-9.2.2/src/setuptools_scm/_file_finders/pathtools.py000066400000000000000000000002631507525030000255540ustar00rootroot00000000000000from __future__ import annotations import os from setuptools_scm import _types as _t def norm_real(path: _t.PathT) -> str: return os.path.normcase(os.path.realpath(path)) setuptools-scm-9.2.2/src/setuptools_scm/_get_version_impl.py000066400000000000000000000214501507525030000244540ustar00rootroot00000000000000from __future__ import annotations import dataclasses import logging import re import warnings from pathlib import Path from typing import Any from typing import NoReturn from typing import Pattern from . import _config from . import _entrypoints from . import _run_cmd from . import _types as _t from ._config import Configuration from ._overrides import _read_pretended_version_for from ._version_cls import _validate_version_cls from .version import ScmVersion from .version import format_version as _format_version EMPTY_TAG_REGEX_DEPRECATION = DeprecationWarning( "empty regex for tag regex is invalid, using default" ) _log = logging.getLogger(__name__) def parse_scm_version(config: Configuration) -> ScmVersion | None: try: if config.parse is not None: parse_result = config.parse(config.absolute_root, config=config) if parse_result is not None and not isinstance(parse_result, ScmVersion): raise TypeError( f"version parse result was {str!r}\n" "please return a parsed version (ScmVersion)" ) return parse_result else: return _entrypoints.version_from_entrypoint( config, entrypoint="setuptools_scm.parse_scm", root=config.absolute_root, ) except _run_cmd.CommandNotFoundError as e: _log.exception("command %s not found while parsing the scm, using fallbacks", e) return None def parse_fallback_version(config: Configuration) -> ScmVersion | None: return _entrypoints.version_from_entrypoint( config, entrypoint="setuptools_scm.parse_scm_fallback", root=config.fallback_root, ) def parse_version(config: Configuration) -> ScmVersion | None: # First try to get a version from the normal flow scm_version = ( _read_pretended_version_for(config) or parse_scm_version(config) or parse_fallback_version(config) ) # Apply any metadata overrides to the version we found from ._overrides import _apply_metadata_overrides return _apply_metadata_overrides(scm_version, config) def write_version_files( config: Configuration, version: str, scm_version: ScmVersion ) -> None: if config.write_to is not None: from ._integration.dump_version import dump_version dump_version( root=config.root, version=version, scm_version=scm_version, write_to=config.write_to, template=config.write_to_template, ) if config.version_file: from ._integration.dump_version import write_version_to_path version_file = Path(config.version_file) assert not version_file.is_absolute(), f"{version_file=}" # todo: use a better name than fallback root assert config.relative_to is not None target = Path(config.relative_to).parent.joinpath(version_file) write_version_to_path( target, template=config.version_file_template, version=version, scm_version=scm_version, ) def _get_version( config: Configuration, force_write_version_files: bool | None = None ) -> str | None: parsed_version = parse_version(config) if parsed_version is None: return None version_string = _format_version(parsed_version) if force_write_version_files is None: force_write_version_files = True warnings.warn( "force_write_version_files ought to be set," " presuming the legacy True value", DeprecationWarning, ) if force_write_version_files: write_version_files(config, version=version_string, scm_version=parsed_version) return version_string def _find_scm_in_parents(config: Configuration) -> Path | None: """ Search parent directories for SCM repositories when relative_to is not set. Uses the existing entrypoint system for SCM discovery. """ if config.search_parent_directories: return None searching_config = dataclasses.replace(config, search_parent_directories=True) from .discover import iter_matching_entrypoints for _ep in iter_matching_entrypoints( config.absolute_root, "setuptools_scm.parse_scm", searching_config ): # xxx: iter_matching_entrypoints should return the parent directory, we do a hack atm assert searching_config.parent is not None return Path(searching_config.parent) return None def _version_missing(config: Configuration) -> NoReturn: base_error = ( f"setuptools-scm was unable to detect version for {config.absolute_root}.\n\n" ) # If relative_to is not set, check for SCM repositories in parent directories scm_parent = None if config.relative_to is None: scm_parent = _find_scm_in_parents(config) if scm_parent is not None: # Found an SCM repository in a parent directory error_msg = ( base_error + f"However, a repository was found in a parent directory: {scm_parent}\n\n" f"To fix this, you have a few options:\n\n" f"1. Use the 'relative_to' parameter to specify the file that setuptools-scm should use as reference:\n" f" setuptools_scm.get_version(relative_to=__file__)\n\n" f"2. Enable parent directory search in your configuration:\n" f" [tool.setuptools_scm]\n" f" search_parent_directories = true\n\n" f"3. Change your working directory to the repository root: {scm_parent}\n\n" f"4. Set the root explicitly in your configuration:\n" f" [tool.setuptools_scm]\n" f' root = "{scm_parent}"\n\n' "For more information, see: https://setuptools-scm.readthedocs.io/en/latest/config/" ) else: # No SCM repository found in parent directories either error_msg = ( base_error + "Make sure you're either building from a fully intact git repository " "or PyPI tarballs. Most other sources (such as GitHub's tarballs, a " "git checkout without the .git folder) don't contain the necessary " "metadata and will not work.\n\n" "For example, if you're using pip, instead of " "https://github.com/user/proj/archive/master.zip " "use git+https://github.com/user/proj.git#egg=proj\n\n" "Alternatively, set the version with the environment variable " "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME} as described " "in https://setuptools-scm.readthedocs.io/en/latest/config/" ) raise LookupError(error_msg) def get_version( root: _t.PathT = ".", version_scheme: _t.VERSION_SCHEME = _config.DEFAULT_VERSION_SCHEME, local_scheme: _t.VERSION_SCHEME = _config.DEFAULT_LOCAL_SCHEME, write_to: _t.PathT | None = None, write_to_template: str | None = None, version_file: _t.PathT | None = None, version_file_template: str | None = None, relative_to: _t.PathT | None = None, tag_regex: str | Pattern[str] = _config.DEFAULT_TAG_REGEX, parentdir_prefix_version: str | None = None, fallback_version: str | None = None, fallback_root: _t.PathT = ".", parse: Any | None = None, git_describe_command: _t.CMD_TYPE | None = None, dist_name: str | None = None, version_cls: Any | None = None, normalize: bool = True, search_parent_directories: bool = False, scm: dict[str, Any] | None = None, ) -> str: """ If supplied, relative_to should be a file from which root may be resolved. Typically called by a script or module that is not in the root of the repository to direct setuptools-scm to the root of the repository by supplying ``__file__``. """ version_cls = _validate_version_cls(version_cls, normalize) del normalize tag_regex = parse_tag_regex(tag_regex) # Handle scm parameter by converting it to ScmConfiguration if scm is not None: scm_config = _config.ScmConfiguration.from_data(scm) else: scm_config = _config.ScmConfiguration() # Remove scm from locals() since we handle it separately config_params = locals().copy() config_params.pop("scm", None) config_params.pop("scm_config", None) config = _config.Configuration(scm=scm_config, **config_params) maybe_version = _get_version(config, force_write_version_files=True) if maybe_version is None: _version_missing(config) return maybe_version def parse_tag_regex(tag_regex: str | Pattern[str]) -> Pattern[str]: if isinstance(tag_regex, str): if tag_regex == "": warnings.warn(EMPTY_TAG_REGEX_DEPRECATION) return _config.DEFAULT_TAG_REGEX else: return re.compile(tag_regex) else: return tag_regex setuptools-scm-9.2.2/src/setuptools_scm/_integration/000077500000000000000000000000001507525030000230565ustar00rootroot00000000000000setuptools-scm-9.2.2/src/setuptools_scm/_integration/__init__.py000066400000000000000000000000001507525030000251550ustar00rootroot00000000000000setuptools-scm-9.2.2/src/setuptools_scm/_integration/deprecation.py000066400000000000000000000014221507525030000257240ustar00rootroot00000000000000import warnings from pathlib import Path def warn_dynamic_version(path: Path, section: str, expression: str) -> None: warnings.warn( f"{path}: at [{section}]\n" f"{expression} is forcing setuptools to override the version setuptools-scm did already set\n" "When using setuptools-scm it's invalid to use setuptools dynamic version as well, please remove it.\n" "Setuptools-scm is responsible for setting the version, forcing setuptools to override creates errors." ) def warn_pyproject_setuptools_dynamic_version(path: Path) -> None: warn_dynamic_version(path, "tool.setuptools.dynamic", "version = {attr = ...}") def warn_setup_cfg_dynamic_version(path: Path) -> None: warn_dynamic_version(path, "metadata", "version = attr: ...") setuptools-scm-9.2.2/src/setuptools_scm/_integration/dump_version.py000066400000000000000000000062231507525030000261450ustar00rootroot00000000000000from __future__ import annotations import warnings from pathlib import Path from .. import _types as _t from .._log import log as parent_log from .._version_cls import _version_as_tuple from ..version import ScmVersion log = parent_log.getChild("dump_version") TEMPLATES = { ".py": """\ # file generated by setuptools-scm # don't change, don't track in version control __all__ = [ "__version__", "__version_tuple__", "version", "version_tuple", "__commit_id__", "commit_id", ] TYPE_CHECKING = False if TYPE_CHECKING: from typing import Tuple from typing import Union VERSION_TUPLE = Tuple[Union[int, str], ...] COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE commit_id: COMMIT_ID __commit_id__: COMMIT_ID __version__ = version = {version!r} __version_tuple__ = version_tuple = {version_tuple!r} __commit_id__ = commit_id = {scm_version.short_node!r} """, ".txt": "{version}", } def dump_version( root: _t.PathT, version: str, write_to: _t.PathT, template: str | None = None, scm_version: ScmVersion | None = None, ) -> None: assert isinstance(version, str) root = Path(root) write_to = Path(write_to) if write_to.is_absolute(): # trigger warning on escape write_to.relative_to(root) warnings.warn( f"{write_to=!s} is a absolute path," " please switch to using a relative version file", DeprecationWarning, ) target = write_to else: target = Path(root).joinpath(write_to) write_version_to_path( target, template=template, version=version, scm_version=scm_version ) def _validate_template(target: Path, template: str | None) -> str: if template == "": warnings.warn(f"{template=} looks like a error, using default instead") template = None if template is None: template = TEMPLATES.get(target.suffix) if template is None: raise ValueError( f"bad file format: {target.suffix!r} (of {target})\n" "only *.txt and *.py have a default template" ) else: return template class DummyScmVersion: @property def short_node(self) -> str | None: return None def write_version_to_path( target: Path, template: str | None, version: str, scm_version: ScmVersion | None = None, ) -> None: final_template = _validate_template(target, template) log.debug("dump %s into %s", version, target) version_tuple = _version_as_tuple(version) if scm_version is None: warnings.warn( "write_version_to_path called without scm_version parameter. " "This will be required in a future version. " "Pass scm_version=None explicitly to suppress this warning.", DeprecationWarning, stacklevel=2, ) content = final_template.format( version=version, version_tuple=version_tuple, scm_version=scm_version or DummyScmVersion(), ) target.write_text(content, encoding="utf-8") setuptools-scm-9.2.2/src/setuptools_scm/_integration/pyproject_reading.py000066400000000000000000000220441507525030000271420ustar00rootroot00000000000000from __future__ import annotations import warnings from dataclasses import dataclass from pathlib import Path from typing import Sequence from .. import _log from .. import _types as _t from .._requirement_cls import extract_package_name from .toml import TOML_RESULT from .toml import InvalidTomlError from .toml import read_toml_content log = _log.log.getChild("pyproject_reading") _ROOT = "root" DEFAULT_PYPROJECT_PATH = Path("pyproject.toml") DEFAULT_TOOL_NAME = "setuptools_scm" @dataclass class PyProjectData: path: Path tool_name: str project: TOML_RESULT section: TOML_RESULT is_required: bool section_present: bool project_present: bool build_requires: list[str] @classmethod def for_testing( cls, *, is_required: bool = False, section_present: bool = False, project_present: bool = False, project_name: str | None = None, has_dynamic_version: bool = True, build_requires: list[str] | None = None, local_scheme: str | None = None, ) -> PyProjectData: """Create a PyProjectData instance for testing purposes.""" project: TOML_RESULT if project_name is not None: project = {"name": project_name} assert project_present else: project = {} # If project is present and has_dynamic_version is True, add dynamic=['version'] if project_present and has_dynamic_version: project["dynamic"] = ["version"] if build_requires is None: build_requires = [] if local_scheme is not None: assert section_present section = {"local_scheme": local_scheme} else: section = {} return cls( path=DEFAULT_PYPROJECT_PATH, tool_name=DEFAULT_TOOL_NAME, project=project, section=section, is_required=is_required, section_present=section_present, project_present=project_present, build_requires=build_requires, ) @classmethod def empty( cls, path: Path = DEFAULT_PYPROJECT_PATH, tool_name: str = DEFAULT_TOOL_NAME ) -> PyProjectData: return cls( path=path, tool_name=tool_name, project={}, section={}, is_required=False, section_present=False, project_present=False, build_requires=[], ) @property def project_name(self) -> str | None: return self.project.get("name") @property def project_version(self) -> str | None: """Return the static version from [project] if present. When the project declares dynamic = ["version"], the version is intentionally omitted from [project] and this returns None. """ return self.project.get("version") def should_infer(self) -> bool: """ Determine if setuptools_scm should infer version based on configuration. Infer when: 1. An explicit [tool.setuptools_scm] section is present, OR 2. setuptools-scm[simple] is in build-system.requires AND version is in project.dynamic Returns: True if [tool.setuptools_scm] is present, otherwise False """ # Original behavior: explicit tool section if self.section_present: return True # New behavior: simple extra + dynamic version if self.project_present: dynamic_fields = self.project.get("dynamic", []) if "version" in dynamic_fields: if has_build_package_with_extra( self.build_requires, "setuptools-scm", "simple" ): return True return False def has_build_package( requires: Sequence[str], canonical_build_package_name: str ) -> bool: for requirement in requires: package_name = extract_package_name(requirement) if package_name == canonical_build_package_name: return True return False def has_build_package_with_extra( requires: Sequence[str], canonical_build_package_name: str, extra_name: str ) -> bool: """Check if a build dependency has a specific extra. Args: requires: List of requirement strings from build-system.requires canonical_build_package_name: The canonical package name to look for extra_name: The extra name to check for (e.g., "simple") Returns: True if the package is found with the specified extra """ from .._requirement_cls import Requirement for requirement_string in requires: try: requirement = Requirement(requirement_string) package_name = extract_package_name(requirement_string) if package_name == canonical_build_package_name: if extra_name in requirement.extras: return True except Exception: # If parsing fails, continue to next requirement continue return False def read_pyproject( path: Path = DEFAULT_PYPROJECT_PATH, tool_name: str = DEFAULT_TOOL_NAME, canonical_build_package_name: str = "setuptools-scm", _given_result: _t.GivenPyProjectResult = None, _given_definition: TOML_RESULT | None = None, ) -> PyProjectData: """Read and parse pyproject configuration. This function supports dependency injection for tests via ``_given_result`` and ``_given_definition``. :param path: Path to the pyproject file :param tool_name: The tool section name (default: ``setuptools_scm``) :param canonical_build_package_name: Normalized build requirement name :param _given_result: Optional testing hook. Can be: - ``PyProjectData``: returned directly - ``InvalidTomlError`` | ``FileNotFoundError``: raised directly - ``None``: read from filesystem (default) :param _given_definition: Optional testing hook to provide parsed TOML content. When provided, this dictionary is used instead of reading and parsing the file from disk. Ignored if ``_given_result`` is provided. """ if _given_result is not None: if isinstance(_given_result, PyProjectData): return _given_result if isinstance(_given_result, (InvalidTomlError, FileNotFoundError)): raise _given_result if _given_definition is not None: defn = _given_definition else: defn = read_toml_content(path) requires: list[str] = defn.get("build-system", {}).get("requires", []) is_required = has_build_package(requires, canonical_build_package_name) tool_section = defn.get("tool", {}) section = tool_section.get(tool_name, {}) section_present = tool_name in tool_section if not section_present: log.warning( "toml section missing %r does not contain a tool.%s section", path, tool_name, ) project = defn.get("project", {}) project_present = "project" in defn pyproject_data = PyProjectData( path, tool_name, project, section, is_required, section_present, project_present, requires, ) setuptools_dynamic_version = ( defn.get("tool", {}) .get("setuptools", {}) .get("dynamic", {}) .get("version", None) ) # Only warn if setuptools-scm is being used for version inference # (not just file finding). When only file finders are used, it's valid # to use tool.setuptools.dynamic.version for versioning. if setuptools_dynamic_version is not None and pyproject_data.should_infer(): from .deprecation import warn_pyproject_setuptools_dynamic_version warn_pyproject_setuptools_dynamic_version(path) return pyproject_data def get_args_for_pyproject( pyproject: PyProjectData, dist_name: str | None, kwargs: TOML_RESULT, ) -> TOML_RESULT: """drops problematic details and figures the distribution name""" section = pyproject.section.copy() kwargs = kwargs.copy() if "relative_to" in section: relative = section.pop("relative_to") warnings.warn( f"{pyproject.path}: at [tool.{pyproject.tool_name}]\n" f"ignoring value relative_to={relative!r}" " as its always relative to the config file" ) if "dist_name" in section: if dist_name is None: dist_name = section.pop("dist_name") else: assert dist_name == section["dist_name"] section.pop("dist_name") if dist_name is None: # minimal pep 621 support for figuring the pretend keys dist_name = pyproject.project_name if _ROOT in kwargs: if kwargs[_ROOT] is None: kwargs.pop(_ROOT, None) elif _ROOT in section: if section[_ROOT] != kwargs[_ROOT]: warnings.warn( f"root {section[_ROOT]} is overridden" f" by the cli arg {kwargs[_ROOT]}" ) section.pop(_ROOT, None) return {"dist_name": dist_name, **section, **kwargs} setuptools-scm-9.2.2/src/setuptools_scm/_integration/setup_cfg.py000066400000000000000000000024261507525030000254130ustar00rootroot00000000000000from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path import setuptools @dataclass class SetuptoolsBasicData: path: Path name: str | None version: str | None def read_setup_cfg(input: str | os.PathLike[str] = "setup.cfg") -> SetuptoolsBasicData: """Parse setup.cfg and return unified data. Does not raise if file is missing.""" import configparser path = Path(input) parser = configparser.ConfigParser() parser.read([input], encoding="utf-8") name = parser.get("metadata", "name", fallback=None) version = parser.get("metadata", "version", fallback=None) if version is not None and "attr" in version: from .deprecation import warn_setup_cfg_dynamic_version warn_setup_cfg_dynamic_version(path) version = None return SetuptoolsBasicData(path=path, name=name, version=version) def extract_from_legacy( dist: setuptools.Distribution, *, _given_legacy_data: SetuptoolsBasicData | None = None, ) -> SetuptoolsBasicData: base = _given_legacy_data if _given_legacy_data is not None else read_setup_cfg() if base.name is None: base.name = dist.metadata.name if base.version is None: base.version = dist.metadata.version return base setuptools-scm-9.2.2/src/setuptools_scm/_integration/setuptools.py000066400000000000000000000120261507525030000256520ustar00rootroot00000000000000from __future__ import annotations import logging import warnings from typing import Any from typing import Callable import setuptools from .. import _types as _t from .pyproject_reading import PyProjectData from .pyproject_reading import read_pyproject from .setup_cfg import SetuptoolsBasicData from .setup_cfg import extract_from_legacy from .toml import InvalidTomlError from .version_inference import get_version_inference_config log = logging.getLogger(__name__) def _warn_on_old_setuptools(_version: str = setuptools.__version__) -> None: if int(_version.split(".")[0]) < 61: warnings.warn( RuntimeWarning( f""" ERROR: setuptools=={_version} is used in combination with setuptools-scm>=8.x Your build configuration is incomplete and previously worked by accident! setuptools-scm requires setuptools>=61 (recommended: >=80) Suggested workaround if applicable: - migrating from the deprecated setup_requires mechanism to pep517/518 and using a pyproject.toml to declare build dependencies which are reliably pre-installed before running the build tools """ ) ) _warn_on_old_setuptools() def _log_hookstart(hook: str, dist: setuptools.Distribution) -> None: log.debug( "%s %s %s %r", hook, id(dist), id(dist.metadata), {**vars(dist.metadata), "long_description": ...}, ) def get_keyword_overrides( value: bool | dict[str, Any] | Callable[[], dict[str, Any]], ) -> dict[str, Any]: """normalize the version keyword input""" if value is True: return {} elif callable(value): return value() else: assert isinstance(value, dict), "version_keyword expects a dict or True" return value def version_keyword( dist: setuptools.Distribution, keyword: str, value: bool | dict[str, Any] | Callable[[], dict[str, Any]], *, _given_pyproject_data: _t.GivenPyProjectResult = None, _given_legacy_data: SetuptoolsBasicData | None = None, _get_version_inference_config: _t.GetVersionInferenceConfig = get_version_inference_config, ) -> None: """apply version infernce when setup(use_scm_version=...) is used this takes priority over the finalize_options based version """ _log_hookstart("version_keyword", dist) # Parse overrides (integration point responsibility) overrides = get_keyword_overrides(value) assert "dist_name" not in overrides, ( "dist_name may not be specified in the setup keyword " ) legacy_data = extract_from_legacy(dist, _given_legacy_data=_given_legacy_data) dist_name: str | None = legacy_data.name was_set_by_infer = getattr(dist, "_setuptools_scm_version_set_by_infer", False) # Exit early if overrides is empty dict AND version was set by infer if overrides == {} and was_set_by_infer: return # Get pyproject data (support direct injection for tests) try: pyproject_data = read_pyproject(_given_result=_given_pyproject_data) except FileNotFoundError: log.debug("pyproject.toml not found, proceeding with empty configuration") pyproject_data = PyProjectData.empty() except InvalidTomlError as e: log.debug("Configuration issue in pyproject.toml: %s", e) return # Pass None as current_version if overrides is truthy AND version was set by infer current_version = ( None if (overrides and was_set_by_infer) else (legacy_data.version or pyproject_data.project_version) ) result = _get_version_inference_config( dist_name=dist_name, current_version=current_version, pyproject_data=pyproject_data, overrides=overrides, ) result.apply(dist) def infer_version( dist: setuptools.Distribution, *, _given_pyproject_data: _t.GivenPyProjectResult = None, _given_legacy_data: SetuptoolsBasicData | None = None, _get_version_inference_config: _t.GetVersionInferenceConfig = get_version_inference_config, ) -> None: """apply version inference from the finalize_options hook this is the default for pyproject.toml based projects that don't use the use_scm_version keyword if the version keyword is used, it will override the version from this hook as user might have passed custom code version schemes """ _log_hookstart("infer_version", dist) legacy_data = extract_from_legacy(dist, _given_legacy_data=_given_legacy_data) dist_name = legacy_data.name try: pyproject_data = read_pyproject(_given_result=_given_pyproject_data) except FileNotFoundError: log.debug("pyproject.toml not found, skipping infer_version") return except InvalidTomlError as e: log.debug("Configuration issue in pyproject.toml: %s", e) return # Only infer when tool section present per get_version_inference_config result = _get_version_inference_config( dist_name=dist_name, current_version=legacy_data.version or pyproject_data.project_version, pyproject_data=pyproject_data, ) result.apply(dist) setuptools-scm-9.2.2/src/setuptools_scm/_integration/toml.py000066400000000000000000000035321507525030000244060ustar00rootroot00000000000000from __future__ import annotations import sys from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import Dict from typing import TypedDict from typing import cast if sys.version_info >= (3, 11): from tomllib import loads as load_toml else: from tomli import loads as load_toml if TYPE_CHECKING: if sys.version_info >= (3, 10): from typing import TypeAlias else: from typing_extensions import TypeAlias from .. import _log log = _log.log.getChild("toml") TOML_RESULT: TypeAlias = Dict[str, Any] TOML_LOADER: TypeAlias = Callable[[str], TOML_RESULT] class InvalidTomlError(ValueError): """Raised when TOML data cannot be parsed.""" def read_toml_content(path: Path, default: TOML_RESULT | None = None) -> TOML_RESULT: try: data = path.read_text(encoding="utf-8") except FileNotFoundError: if default is None: raise else: log.debug("%s missing, presuming default %r", path, default) return default else: try: return load_toml(data) except Exception as e: # tomllib/tomli raise different decode errors raise InvalidTomlError(f"Invalid TOML in {path}") from e class _CheatTomlData(TypedDict): cheat: dict[str, Any] def load_toml_or_inline_map(data: str | None) -> dict[str, Any]: """ load toml data - with a special hack if only a inline map is given """ if not data: return {} try: if data[0] == "{": data = "cheat=" + data loaded: _CheatTomlData = cast(_CheatTomlData, load_toml(data)) return loaded["cheat"] return load_toml(data) except Exception as e: # tomllib/tomli raise different decode errors raise InvalidTomlError("Invalid TOML content") from e setuptools-scm-9.2.2/src/setuptools_scm/_integration/version_inference.py000066400000000000000000000101431507525030000271320ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING from typing import Any from typing import Union from setuptools import Distribution from .. import _log if TYPE_CHECKING: from .pyproject_reading import PyProjectData log = _log.log.getChild("version_inference") @dataclass class VersionInferenceConfig: """Configuration for version inference.""" dist_name: str | None pyproject_data: PyProjectData | None overrides: dict[str, Any] | None def apply(self, dist: Distribution) -> None: """Apply version inference to the distribution.""" version_string = infer_version_string( self.dist_name, self.pyproject_data, # type: ignore[arg-type] self.overrides, force_write_version_files=True, ) dist.metadata.version = version_string # Mark that this version was set by infer_version if overrides is None (infer_version context) if self.overrides is None: dist._setuptools_scm_version_set_by_infer = True # type: ignore[attr-defined] @dataclass class VersionInferenceWarning: """Error message for user.""" message: str def apply(self, dist: Distribution) -> None: """Apply error handling to the distribution.""" import warnings warnings.warn(self.message) @dataclass(frozen=True) class VersionInferenceNoOp: """No operation result - silent skip.""" def apply(self, dist: Distribution) -> None: """Apply no-op to the distribution.""" VersionInferenceResult = Union[ VersionInferenceConfig, # Proceed with inference VersionInferenceWarning, # Show warning VersionInferenceNoOp, # Don't infer (silent) ] def infer_version_string( dist_name: str | None, pyproject_data: PyProjectData, overrides: dict[str, Any] | None = None, *, force_write_version_files: bool = False, ) -> str: """ Compute the inferred version string from the given inputs without requiring a setuptools Distribution instance. This is a pure helper that simplifies integration tests by avoiding file I/O and side effects on a Distribution. Parameters: dist_name: Optional distribution name (used for overrides and env scoping) pyproject_data: Parsed PyProjectData (may be constructed via for_testing()) overrides: Optional override configuration (same keys as [tool.setuptools_scm]) force_write_version_files: When True, apply write_to/version_file effects Returns: The computed version string. """ from .. import _config as _config_module from .._get_version_impl import _get_version from .._get_version_impl import _version_missing config = _config_module.Configuration.from_file( dist_name=dist_name, pyproject_data=pyproject_data, **(overrides or {}) ) maybe_version = _get_version( config, force_write_version_files=force_write_version_files ) if maybe_version is None: _version_missing(config) return maybe_version def get_version_inference_config( dist_name: str | None, current_version: str | None, pyproject_data: PyProjectData, overrides: dict[str, Any] | None = None, ) -> VersionInferenceResult: """ Determine whether and how to perform version inference. Args: dist_name: The distribution name current_version: Current version if any pyproject_data: PyProjectData from parser (None if file doesn't exist) overrides: Override configuration (None for no overrides) Returns: VersionInferenceResult with the decision and configuration """ config = VersionInferenceConfig( dist_name=dist_name, pyproject_data=pyproject_data, overrides=overrides, ) inference_implied = pyproject_data.should_infer() or overrides is not None if inference_implied: if current_version is None: return config else: return VersionInferenceWarning( f"version of {dist_name} already set", ) else: return VersionInferenceNoOp() setuptools-scm-9.2.2/src/setuptools_scm/_log.py000066400000000000000000000040261507525030000216700ustar00rootroot00000000000000""" logging helpers, supports vendoring """ from __future__ import annotations import contextlib import logging import os import sys from typing import IO from typing import Iterator from typing import Mapping log = logging.getLogger(__name__.rsplit(".", 1)[0]) log.propagate = False class AlwaysStdErrHandler(logging.StreamHandler): # type: ignore[type-arg] def __init__(self) -> None: super().__init__(sys.stderr) @property def stream(self) -> IO[str]: return sys.stderr @stream.setter def stream(self, value: IO[str]) -> None: assert value is sys.stderr def make_default_handler() -> logging.Handler: try: from rich.console import Console console = Console(stderr=True) from rich.logging import RichHandler return RichHandler(console=console) except ImportError: last_resort = logging.lastResort assert last_resort is not None return last_resort _default_handler = make_default_handler() log.addHandler(_default_handler) def _default_log_level(_env: Mapping[str, str] = os.environ) -> int: val: str | None = _env.get("SETUPTOOLS_SCM_DEBUG") return logging.WARNING if val is None else logging.DEBUG log.setLevel(_default_log_level()) @contextlib.contextmanager def defer_to_pytest() -> Iterator[None]: log.propagate = True old_level = log.level log.setLevel(logging.NOTSET) log.removeHandler(_default_handler) try: yield finally: log.addHandler(_default_handler) log.propagate = False log.setLevel(old_level) @contextlib.contextmanager def enable_debug(handler: logging.Handler = _default_handler) -> Iterator[None]: log.addHandler(handler) old_level = log.level log.setLevel(logging.DEBUG) old_handler_level = handler.level handler.setLevel(logging.DEBUG) try: yield finally: log.setLevel(old_level) handler.setLevel(old_handler_level) if handler is not _default_handler: log.removeHandler(handler) setuptools-scm-9.2.2/src/setuptools_scm/_modify_version.py000066400000000000000000000033121507525030000241400ustar00rootroot00000000000000from __future__ import annotations import re from . import _types as _t def strip_local(version_string: str) -> str: public = version_string.partition("+")[0] return public def _add_post(version: str) -> str: if "post" in version: raise ValueError( f"{version} already is a post release, refusing to guess the update" ) return f"{version}.post1" def _bump_dev(version: str) -> str | None: if ".dev" not in version: return None prefix, tail = version.rsplit(".dev", 1) if tail != "0": raise ValueError( "choosing custom numbers for the `.devX` distance " "is not supported.\n " f"The {version} can't be bumped\n" "Please drop the tag or create a new supported one ending in .dev0" ) return prefix def _bump_regex(version: str) -> str: match = re.match(r"(.*?)(\d+)$", version) if match is None: raise ValueError( f"{version} does not end with a number to bump, " "please correct or use a custom version scheme" ) else: prefix, tail = match.groups() return f"{prefix}{int(tail) + 1}" def _format_local_with_time(version: _t.SCMVERSION, time_format: str) -> str: if version.exact or version.node is None: return version.format_choice( "", "+d{time:{time_format}}", time_format=time_format ) else: return version.format_choice( "+{node}", "+{node}.d{time:{time_format}}", time_format=time_format ) def _dont_guess_next_version(tag_version: _t.SCMVERSION) -> str: version = strip_local(str(tag_version.tag)) return _bump_dev(version) or _add_post(version) setuptools-scm-9.2.2/src/setuptools_scm/_node_utils.py000066400000000000000000000024361507525030000232570ustar00rootroot00000000000000"""Private utilities for consistent node ID handling across SCM backends.""" from __future__ import annotations # Standard node ID length used across all SCM backends _NODE_ID_LENGTH = 10 def _slice_node_id(node_id: str) -> str: """ Slice a node ID to a consistent length. This ensures that all SCM backends (git, mercurial, archival) return the same length node IDs for consistency. Args: node_id: The full node ID/hash from the SCM Returns: The node ID sliced to the standard length """ return node_id[:_NODE_ID_LENGTH] def _format_node_for_output(node_id: str | None) -> str | None: """ Format a node ID for output, applying consistent slicing. Args: node_id: The full node ID/hash from the SCM or None Returns: The node ID sliced to standard length for output, or None if input was None """ if node_id is None: return None # Handle mercurial nodes with 'h' prefix if node_id.startswith("h"): # For mercurial nodes, slice the part after 'h' and reconstruct hg_hash = node_id[1:] # Remove 'h' prefix sliced_hash = _slice_node_id(hg_hash) return "h" + sliced_hash # For git nodes (with or without 'g' prefix) and others return _slice_node_id(node_id) setuptools-scm-9.2.2/src/setuptools_scm/_overrides.py000066400000000000000000000244301507525030000231120ustar00rootroot00000000000000from __future__ import annotations import dataclasses import os from difflib import get_close_matches from typing import Any from typing import Mapping from packaging.utils import canonicalize_name from . import _config from . import _log from . import version from ._integration.toml import load_toml_or_inline_map log = _log.log.getChild("overrides") PRETEND_KEY = "SETUPTOOLS_SCM_PRETEND_VERSION" PRETEND_KEY_NAMED = PRETEND_KEY + "_FOR_{name}" PRETEND_METADATA_KEY = "SETUPTOOLS_SCM_PRETEND_METADATA" PRETEND_METADATA_KEY_NAMED = PRETEND_METADATA_KEY + "_FOR_{name}" def _search_env_vars_with_prefix( prefix: str, dist_name: str, env: Mapping[str, str] ) -> list[tuple[str, str]]: """Search environment variables with a given prefix for potential dist name matches. Args: prefix: The environment variable prefix (e.g., "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_") dist_name: The original dist name to match against env: Environment dictionary to search in Returns: List of (env_var_name, env_var_value) tuples for potential matches """ # Get the canonical name for comparison canonical_dist_name = canonicalize_name(dist_name) matches = [] for env_var, value in env.items(): if env_var.startswith(prefix): suffix = env_var[len(prefix) :] # Normalize the suffix and compare to canonical dist name try: normalized_suffix = canonicalize_name(suffix.lower().replace("_", "-")) if normalized_suffix == canonical_dist_name: matches.append((env_var, value)) except Exception: # If normalization fails for any reason, skip this env var continue return matches def _find_close_env_var_matches( prefix: str, expected_suffix: str, env: Mapping[str, str], threshold: float = 0.6 ) -> list[str]: """Find environment variables with similar suffixes that might be typos. Args: prefix: The environment variable prefix expected_suffix: The expected suffix (canonicalized dist name in env var format) env: Environment dictionary to search in threshold: Similarity threshold for matches (0.0 to 1.0) Returns: List of environment variable names that are close matches """ candidates = [] for env_var in env: if env_var.startswith(prefix): suffix = env_var[len(prefix) :] candidates.append(suffix) # Use difflib to find close matches close_matches = get_close_matches( expected_suffix, candidates, n=3, cutoff=threshold ) return [f"{prefix}{match}" for match in close_matches if match != expected_suffix] def read_named_env( *, tool: str = "SETUPTOOLS_SCM", name: str, dist_name: str | None, env: Mapping[str, str] = os.environ, ) -> str | None: """Read a named environment variable, with fallback search for dist-specific variants. This function first tries the standard normalized environment variable name. If that's not found and a dist_name is provided, it searches for alternative normalizations and warns about potential issues. Args: tool: The tool prefix (default: "SETUPTOOLS_SCM") name: The environment variable name component dist_name: The distribution name for dist-specific variables env: Environment dictionary to search in (defaults to os.environ) Returns: The environment variable value if found, None otherwise """ # First try the generic version generic_val = env.get(f"{tool}_{name}") if dist_name is not None: # Normalize the dist name using packaging.utils.canonicalize_name canonical_dist_name = canonicalize_name(dist_name) env_var_dist_name = canonical_dist_name.replace("-", "_").upper() expected_env_var = f"{tool}_{name}_FOR_{env_var_dist_name}" # Try the standard normalized name first val = env.get(expected_env_var) if val is not None: return val # If not found, search for alternative normalizations prefix = f"{tool}_{name}_FOR_" alternative_matches = _search_env_vars_with_prefix(prefix, dist_name, env) if alternative_matches: # Found alternative matches - use the first one but warn env_var, value = alternative_matches[0] log.warning( "Found environment variable '%s' for dist name '%s', " "but expected '%s'. Consider using the standard normalized name.", env_var, dist_name, expected_env_var, ) if len(alternative_matches) > 1: other_vars = [var for var, _ in alternative_matches[1:]] log.warning( "Multiple alternative environment variables found: %s. Using '%s'.", other_vars, env_var, ) return value # No exact or alternative matches found - look for potential typos close_matches = _find_close_env_var_matches(prefix, env_var_dist_name, env) if close_matches: log.warning( "Environment variable '%s' not found for dist name '%s' " "(canonicalized as '%s'). Did you mean one of these? %s", expected_env_var, dist_name, canonical_dist_name, close_matches, ) return generic_val def _read_pretended_metadata_for( config: _config.Configuration, ) -> dict[str, Any] | None: """read overridden metadata from the environment tries ``SETUPTOOLS_SCM_PRETEND_METADATA`` and ``SETUPTOOLS_SCM_PRETEND_METADATA_FOR_$UPPERCASE_DIST_NAME`` Returns a dictionary with metadata field overrides like: {"node": "g1337beef", "distance": 4} """ log.debug("dist name: %s", config.dist_name) pretended = read_named_env(name="PRETEND_METADATA", dist_name=config.dist_name) if pretended: try: metadata_overrides = load_toml_or_inline_map(pretended) # Validate that only known ScmVersion fields are provided valid_fields = { "tag", "distance", "node", "dirty", "preformatted", "branch", "node_date", "time", } invalid_fields = set(metadata_overrides.keys()) - valid_fields if invalid_fields: log.warning( "Invalid metadata fields in pretend metadata: %s. " "Valid fields are: %s", invalid_fields, valid_fields, ) # Remove invalid fields but continue processing for field in invalid_fields: metadata_overrides.pop(field) return metadata_overrides or None except Exception as e: log.error("Failed to parse pretend metadata: %s", e) return None else: return None def _apply_metadata_overrides( scm_version: version.ScmVersion | None, config: _config.Configuration, ) -> version.ScmVersion | None: """Apply metadata overrides to a ScmVersion object. This function reads pretend metadata from environment variables and applies the overrides to the given ScmVersion. TOML type coercion is used so values should be provided in their correct types (int, bool, datetime, etc.). Args: scm_version: The ScmVersion to apply overrides to, or None config: Configuration object Returns: Modified ScmVersion with overrides applied, or None """ metadata_overrides = _read_pretended_metadata_for(config) if not metadata_overrides: return scm_version if scm_version is None: log.warning( "PRETEND_METADATA specified but no base version found. " "Metadata overrides cannot be applied without a base version." ) return None log.info("Applying metadata overrides: %s", metadata_overrides) # Define type checks and field mappings from datetime import date from datetime import datetime field_specs: dict[str, tuple[type | tuple[type, type], str]] = { "distance": (int, "int"), "dirty": (bool, "bool"), "preformatted": (bool, "bool"), "node_date": (date, "date"), "time": (datetime, "datetime"), "node": ((str, type(None)), "str or None"), "branch": ((str, type(None)), "str or None"), # tag is special - can be multiple types, handled separately } # Apply each override individually using dataclasses.replace for type safety result = scm_version for field, value in metadata_overrides.items(): if field in field_specs: expected_type, type_name = field_specs[field] assert isinstance(value, expected_type), ( f"{field} must be {type_name}, got {type(value).__name__}: {value!r}" ) result = dataclasses.replace(result, **{field: value}) elif field == "tag": # tag can be Version, NonNormalizedVersion, or str - we'll let the assignment handle validation result = dataclasses.replace(result, tag=value) else: # This shouldn't happen due to validation in _read_pretended_metadata_for log.warning("Unknown field '%s' in metadata overrides", field) # Ensure config is preserved (should not be overridden) assert result.config is config, "Config must be preserved during metadata overrides" return result def _read_pretended_version_for( config: _config.Configuration, ) -> version.ScmVersion | None: """read a a overridden version from the environment tries ``SETUPTOOLS_SCM_PRETEND_VERSION`` and ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_$UPPERCASE_DIST_NAME`` """ log.debug("dist name: %s", config.dist_name) pretended = read_named_env(name="PRETEND_VERSION", dist_name=config.dist_name) if pretended: return version.meta(tag=pretended, preformatted=True, config=config) else: return None def read_toml_overrides(dist_name: str | None) -> dict[str, Any]: data = read_named_env(name="OVERRIDES", dist_name=dist_name) return load_toml_or_inline_map(data) setuptools-scm-9.2.2/src/setuptools_scm/_requirement_cls.py000066400000000000000000000021141507525030000243040ustar00rootroot00000000000000from __future__ import annotations __all__ = ["Requirement", "extract_package_name"] try: from packaging.requirements import Requirement from packaging.utils import canonicalize_name except ImportError: from setuptools.extern.packaging.requirements import ( # type: ignore[import-not-found,no-redef] Requirement as Requirement, ) from setuptools.extern.packaging.utils import ( # type: ignore[import-not-found,no-redef] canonicalize_name as canonicalize_name, ) from . import _log log = _log.log.getChild("requirement_cls") def extract_package_name(requirement_string: str) -> str: """Extract the canonical package name from a requirement string. This function uses packaging.requirements.Requirement to properly parse the requirement and extract the package name, handling all edge cases that the custom regex-based approach might miss. Args: requirement_string: The requirement string to parse Returns: The package name as a string """ return canonicalize_name(Requirement(requirement_string).name) setuptools-scm-9.2.2/src/setuptools_scm/_run_cmd.py000066400000000000000000000140611507525030000225360ustar00rootroot00000000000000from __future__ import annotations import os import shlex import subprocess import textwrap import warnings from typing import TYPE_CHECKING from typing import Callable from typing import Final from typing import Mapping from typing import Sequence from typing import TypeVar from typing import overload from . import _log from . import _types as _t if TYPE_CHECKING: BaseCompletedProcess = subprocess.CompletedProcess[str] else: BaseCompletedProcess = subprocess.CompletedProcess # pick 40 seconds # unfortunately github CI for windows sometimes needs # up to 30 seconds to start a command def _get_timeout(env: Mapping[str, str]) -> int: return int(env.get("SETUPTOOLS_SCM_SUBPROCESS_TIMEOUT") or 40) BROKEN_TIMEOUT: Final[int] = _get_timeout(os.environ) log = _log.log.getChild("run_cmd") PARSE_RESULT = TypeVar("PARSE_RESULT") T = TypeVar("T") class CompletedProcess(BaseCompletedProcess): @classmethod def from_raw( cls, input: BaseCompletedProcess, strip: bool = True ) -> CompletedProcess: return cls( args=input.args, returncode=input.returncode, stdout=input.stdout.strip() if strip and input.stdout else input.stdout, stderr=input.stderr.strip() if strip and input.stderr else input.stderr, ) @overload def parse_success( self, parse: Callable[[str], PARSE_RESULT], default: None = None, error_msg: str | None = None, ) -> PARSE_RESULT | None: ... @overload def parse_success( self, parse: Callable[[str], PARSE_RESULT], default: T, error_msg: str | None = None, ) -> PARSE_RESULT | T: ... def parse_success( self, parse: Callable[[str], PARSE_RESULT], default: T | None = None, error_msg: str | None = None, ) -> PARSE_RESULT | T | None: if self.returncode: if error_msg: log.warning("%s %s", error_msg, self) return default else: return parse(self.stdout) KEEP_GIT_ENV = ( "GIT_CEILING_DIRECTORIES", "GIT_EXEC_PATH", "GIT_SSH", "GIT_SSH_COMMAND", "GIT_AUTHOR_DATE", "GIT_COMMITTER_DATE", ) def no_git_env(env: Mapping[str, str]) -> dict[str, str]: # adapted from pre-commit # Too many bugs dealing with environment variables and GIT: # https://github.com/pre-commit/pre-commit/issues/300 # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running # pre-commit hooks # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE # while running pre-commit hooks in submodules. # GIT_DIR: Causes git clone to clone wrong thing # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit for k, v in env.items(): if k.startswith("GIT_"): log.debug("%s: %s", k, v) return { k: v for k, v in env.items() if not k.startswith("GIT_") or k in KEEP_GIT_ENV } def avoid_pip_isolation(env: Mapping[str, str]) -> dict[str, str]: """ pip build isolation can break Mercurial (see https://github.com/pypa/pip/issues/10635) pip uses PYTHONNOUSERSITE and a path in PYTHONPATH containing "pip-build-env-". """ new_env = {k: v for k, v in env.items() if k != "PYTHONNOUSERSITE"} if "PYTHONPATH" not in new_env: return new_env new_env["PYTHONPATH"] = os.pathsep.join( [ path for path in new_env["PYTHONPATH"].split(os.pathsep) if "-build-env-" not in path ] ) return new_env def ensure_stripped_str(str_or_bytes: str | bytes) -> str: if isinstance(str_or_bytes, str): return str_or_bytes.strip() else: return str_or_bytes.decode("utf-8", "surrogateescape").strip() def run( cmd: _t.CMD_TYPE, cwd: _t.PathT, *, strip: bool = True, trace: bool = True, timeout: int | None = None, check: bool = False, ) -> CompletedProcess: if isinstance(cmd, str): cmd = shlex.split(cmd) else: cmd = [os.fspath(x) for x in cmd] cmd_4_trace = " ".join(map(_unsafe_quote_for_display, cmd)) log.debug("at %s\n $ %s ", cwd, cmd_4_trace) if timeout is None: timeout = BROKEN_TIMEOUT res = subprocess.run( cmd, capture_output=True, cwd=os.fspath(cwd), env=dict( avoid_pip_isolation(no_git_env(os.environ)), # os.environ, # try to disable i18n, but still allow UTF-8 encoded text. LC_ALL="C.UTF-8", LANGUAGE="", HGPLAIN="1", ), text=True, encoding="utf-8", timeout=timeout, ) res = CompletedProcess.from_raw(res, strip=strip) if trace: if res.stdout: log.debug("out:\n%s", textwrap.indent(res.stdout, " ")) if res.stderr: log.debug("err:\n%s", textwrap.indent(res.stderr, " ")) if res.returncode: log.debug("ret: %s", res.returncode) if check: res.check_returncode() return res def _unsafe_quote_for_display(item: _t.PathT) -> str: # give better results than shlex.join in our cases text = os.fspath(item) return text if all(c not in text for c in " {[:") else f'"{text}"' def has_command( name: str, args: Sequence[str] = ["version"], warn: bool = True ) -> bool: try: p = run([name, *args], cwd=".") if p.returncode != 0: log.error("Command '%s' returned non-zero. This is stderr:", name) log.error(p.stderr) except OSError as e: log.warning("command %s missing: %s", name, e) res = False except subprocess.TimeoutExpired as e: log.warning("command %s timed out %s", name, e) res = False else: res = not p.returncode if not res and warn: warnings.warn(f"{name!r} was not found", category=RuntimeWarning) return res class CommandNotFoundError(LookupError, FileNotFoundError): pass def require_command(name: str) -> None: if not has_command(name, warn=False): raise CommandNotFoundError(name) setuptools-scm-9.2.2/src/setuptools_scm/_types.py000066400000000000000000000033451507525030000222560ustar00rootroot00000000000000from __future__ import annotations import os from typing import TYPE_CHECKING from typing import Callable from typing import List from typing import Protocol from typing import Sequence from typing import Tuple from typing import Union from setuptools import Distribution if TYPE_CHECKING: import sys if sys.version_info >= (3, 10): from typing import TypeAlias else: from typing_extensions import TypeAlias from . import version from ._integration.pyproject_reading import PyProjectData from ._integration.toml import InvalidTomlError PathT: TypeAlias = Union["os.PathLike[str]", str] CMD_TYPE: TypeAlias = Union[Sequence[PathT], str] VERSION_SCHEME: TypeAlias = Union[str, Callable[["version.ScmVersion"], str]] VERSION_SCHEMES: TypeAlias = Union[List[str], Tuple[str, ...], VERSION_SCHEME] SCMVERSION: TypeAlias = "version.ScmVersion" # Git pre-parse function types GIT_PRE_PARSE: TypeAlias = Union[str, None] # Testing injection types for configuration reading GivenPyProjectResult: TypeAlias = Union[ "PyProjectData", "InvalidTomlError", FileNotFoundError, None ] class VersionInferenceApplicable(Protocol): """A result object from version inference decision that can be applied to a dist.""" def apply(self, dist: Distribution) -> None: # pragma: no cover - structural type ... class GetVersionInferenceConfig(Protocol): """Callable protocol for the decision function used by integration points.""" def __call__( self, dist_name: str | None, current_version: str | None, pyproject_data: PyProjectData, overrides: dict[str, object] | None = None, ) -> VersionInferenceApplicable: # pragma: no cover - structural type ... setuptools-scm-9.2.2/src/setuptools_scm/_version_cls.py000066400000000000000000000062701507525030000234400ustar00rootroot00000000000000from __future__ import annotations from typing import Type from typing import Union from typing import cast try: from packaging.version import InvalidVersion from packaging.version import Version as Version except ImportError: from setuptools.extern.packaging.version import ( # type: ignore[import-not-found, no-redef] InvalidVersion, ) from setuptools.extern.packaging.version import ( # type: ignore[no-redef] Version as Version, ) from . import _log log = _log.log.getChild("version_cls") class NonNormalizedVersion(Version): """A non-normalizing version handler. You can use this class to preserve version verification but skip normalization. For example you can use this to avoid git release candidate version tags ("1.0.0-rc1") to be normalized to "1.0.0rc1". Only use this if you fully trust the version tags. """ def __init__(self, version: str) -> None: # parse and validate using parent super().__init__(version) # store raw for str self._raw_version = version def __str__(self) -> str: # return the non-normalized version (parent returns the normalized) return self._raw_version def __repr__(self) -> str: # same pattern as parent return f"" def _version_as_tuple(version_str: str) -> tuple[int | str, ...]: try: parsed_version = Version(version_str) except InvalidVersion as e: log.error("failed to parse version %s: %s", e, version_str) return (version_str,) else: version_fields: tuple[int | str, ...] = parsed_version.release if parsed_version.epoch: version_fields = (f"{parsed_version.epoch}!", *version_fields) if parsed_version.pre is not None: version_fields += (f"{parsed_version.pre[0]}{parsed_version.pre[1]}",) if parsed_version.post is not None: version_fields += (f"post{parsed_version.post}",) if parsed_version.dev is not None: version_fields += (f"dev{parsed_version.dev}",) if parsed_version.local is not None: version_fields += (parsed_version.local,) return version_fields _VersionT = Union[Version, NonNormalizedVersion] def import_name(name: str) -> object: import importlib pkg_name, cls_name = name.rsplit(".", 1) pkg = importlib.import_module(pkg_name) return getattr(pkg, cls_name) def _validate_version_cls( version_cls: type[_VersionT] | str | None, normalize: bool ) -> type[_VersionT]: if not normalize: if version_cls is not None: raise ValueError( "Providing a custom `version_cls` is not permitted when " "`normalize=False`" ) return NonNormalizedVersion # Use `version_cls` if provided, default to packaging or pkg_resources elif version_cls is None: return Version elif isinstance(version_cls, str): try: return cast(Type[_VersionT], import_name(version_cls)) except Exception: raise ValueError(f"Unable to import version_cls='{version_cls}'") from None else: return version_cls setuptools-scm-9.2.2/src/setuptools_scm/discover.py000066400000000000000000000040251507525030000225650ustar00rootroot00000000000000from __future__ import annotations import os from pathlib import Path from typing import TYPE_CHECKING from typing import Iterable from typing import Iterator from . import _entrypoints from . import _log from . import _types as _t from ._config import Configuration if TYPE_CHECKING: from ._entrypoints import im log = _log.log.getChild("discover") def walk_potential_roots(root: _t.PathT, search_parents: bool = True) -> Iterator[Path]: """ Iterate though a path and each of its parents. :param root: File path. :param search_parents: If ``False`` the parents are not considered. """ root = Path(root) yield root if search_parents: yield from root.parents def match_entrypoint(root: _t.PathT, name: str) -> bool: """ Consider a ``root`` as entry-point. :param root: File path. :param name: Subdirectory name. :return: ``True`` if a subdirectory ``name`` exits in ``root``. """ if os.path.exists(os.path.join(root, name)): if not os.path.isabs(name): return True log.debug("ignoring bad ep %s", name) return False # blocked entrypints from legacy plugins _BLOCKED_EP_TARGETS = {"setuptools_scm_git_archive:parse"} def iter_matching_entrypoints( root: _t.PathT, entrypoint: str, config: Configuration ) -> Iterable[im.EntryPoint]: """ Consider different entry-points in ``root`` and optionally its parents. :param root: File path. :param entrypoint: Entry-point to consider. :param config: Configuration, read ``search_parent_directories``, write found parent to ``parent``. """ log.debug("looking for ep %s in %s", entrypoint, root) for wd in walk_potential_roots(root, config.search_parent_directories): for ep in _entrypoints.entry_points(group=entrypoint): if ep.value in _BLOCKED_EP_TARGETS: continue if match_entrypoint(wd, ep.name): log.debug("found ep %s in %s", ep, wd) config.parent = wd yield ep setuptools-scm-9.2.2/src/setuptools_scm/fallbacks.py000066400000000000000000000026501507525030000226730ustar00rootroot00000000000000from __future__ import annotations import logging import os from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: from . import _types as _t from . import Configuration from .integration import data_from_mime from .version import ScmVersion from .version import meta from .version import tag_to_version log = logging.getLogger(__name__) _UNKNOWN = "UNKNOWN" def parse_pkginfo(root: _t.PathT, config: Configuration) -> ScmVersion | None: pkginfo = Path(root) / "PKG-INFO" log.debug("pkginfo %s", pkginfo) data = data_from_mime(pkginfo) version = data.get("Version", _UNKNOWN) if version != _UNKNOWN: return meta(version, preformatted=True, config=config) else: return None def fallback_version(root: _t.PathT, config: Configuration) -> ScmVersion | None: if config.parentdir_prefix_version is not None: _, parent_name = os.path.split(os.path.abspath(root)) if parent_name.startswith(config.parentdir_prefix_version): version = tag_to_version( parent_name[len(config.parentdir_prefix_version) :], config ) if version is not None: return meta(str(version), preformatted=True, config=config) if config.fallback_version is not None: log.debug("FALLBACK %s", config.fallback_version) return meta(config.fallback_version, preformatted=True, config=config) return None setuptools-scm-9.2.2/src/setuptools_scm/git.py000066400000000000000000000351641507525030000215420ustar00rootroot00000000000000from __future__ import annotations import dataclasses import logging import os import re import shlex import sys import warnings from datetime import date from datetime import datetime from datetime import timezone from enum import Enum from os.path import samefile from pathlib import Path from typing import TYPE_CHECKING from typing import Callable from typing import Sequence from . import Configuration from . import _types as _t from . import discover from ._run_cmd import CompletedProcess as _CompletedProcess from ._run_cmd import require_command as _require_command from ._run_cmd import run as _run from .integration import data_from_mime from .scm_workdir import Workdir from .scm_workdir import get_latest_file_mtime from .version import ScmVersion from .version import meta from .version import tag_to_version if TYPE_CHECKING: from . import hg_git log = logging.getLogger(__name__) REF_TAG_RE = re.compile(r"(?<=\btag: )([^,]+)\b") DESCRIBE_UNSUPPORTED = "%(describe" # If testing command in shell make sure to quote the match argument like # '*[0-9]*' as it will expand before being sent to git if there are any matching # files in current directory. DEFAULT_DESCRIBE = [ "git", "describe", "--dirty", "--tags", "--long", "--abbrev=40", "--match", "*[0-9]*", ] class GitPreParse(Enum): """Available git pre-parse functions""" WARN_ON_SHALLOW = "warn_on_shallow" FAIL_ON_SHALLOW = "fail_on_shallow" FETCH_ON_SHALLOW = "fetch_on_shallow" FAIL_ON_MISSING_SUBMODULES = "fail_on_missing_submodules" def run_git( args: Sequence[str | os.PathLike[str]], repo: Path, *, check: bool = False, timeout: int | None = None, ) -> _CompletedProcess: return _run( ["git", "--git-dir", repo / ".git", *args], cwd=repo, check=check, timeout=timeout, ) class GitWorkdir(Workdir): """experimental, may change at any time""" @classmethod def from_potential_worktree(cls, wd: _t.PathT) -> GitWorkdir | None: wd = Path(wd).resolve() real_wd = run_git(["rev-parse", "--show-prefix"], wd).parse_success(parse=str) if real_wd is None: return None else: real_wd = real_wd[:-1] # remove the trailing pathsep if not real_wd: real_wd = os.fspath(wd) else: str_wd = os.fspath(wd) from ._compat import strip_path_suffix real_wd = strip_path_suffix(str_wd, real_wd) log.debug("real root %s", real_wd) if not samefile(real_wd, wd): return None return cls(Path(real_wd)) def is_dirty(self) -> bool: return run_git( ["status", "--porcelain", "--untracked-files=no"], self.path ).parse_success( parse=bool, default=False, ) def get_branch(self) -> str | None: return run_git( ["rev-parse", "--abbrev-ref", "HEAD"], self.path, ).parse_success( parse=str, error_msg="branch err (abbrev-err)", ) or run_git( ["symbolic-ref", "--short", "HEAD"], self.path, ).parse_success( parse=str, error_msg="branch err (symbolic-ref)", ) def get_head_date(self) -> date | None: def parse_timestamp(timestamp_text: str) -> date | None: if "%c" in timestamp_text: log.warning("git too old -> timestamp is %r", timestamp_text) return None if sys.version_info < (3, 11) and timestamp_text.endswith("Z"): timestamp_text = timestamp_text[:-1] + "+00:00" # Convert to UTC to ensure consistent date regardless of local timezone dt = datetime.fromisoformat(timestamp_text) log.debug("dt: %s", dt) dt_utc = dt.astimezone(timezone.utc).date() log.debug("dt utc: %s", dt_utc) return dt_utc res = run_git( [ *("-c", "log.showSignature=false"), *("log", "-n", "1", "HEAD"), "--format=%cI", ], self.path, ) return res.parse_success( parse=parse_timestamp, error_msg="logging the iso date for head failed", ) def get_dirty_tag_date(self) -> date | None: """Get the latest modification time of changed files in the working directory. Returns the date of the most recently modified file that has changes, or None if no files are changed or if an error occurs. """ if not self.is_dirty(): return None try: # Get list of changed files changed_files_res = run_git(["diff", "--name-only"], self.path) if changed_files_res.returncode != 0: return None changed_files = changed_files_res.stdout.strip().split("\n") return get_latest_file_mtime(changed_files, self.path) except Exception as e: log.debug("Failed to get dirty tag date: %s", e) return None def is_shallow(self) -> bool: return self.path.joinpath(".git/shallow").is_file() def fetch_shallow(self) -> None: run_git(["fetch", "--unshallow"], self.path, check=True, timeout=240) def node(self) -> str | None: return run_git( ["rev-parse", "--verify", "--quiet", "HEAD"], self.path ).parse_success( parse=str, ) def count_all_nodes(self) -> int: res = run_git(["rev-list", "HEAD"], self.path) return res.stdout.count("\n") + 1 def default_describe(self) -> _CompletedProcess: return run_git(DEFAULT_DESCRIBE[1:], self.path) def warn_on_shallow(wd: GitWorkdir) -> None: """experimental, may change at any time""" if wd.is_shallow(): warnings.warn(f'"{wd.path}" is shallow and may cause errors') def fetch_on_shallow(wd: GitWorkdir) -> None: """experimental, may change at any time""" if wd.is_shallow(): warnings.warn(f'"{wd.path}" was shallow, git fetch was used to rectify') wd.fetch_shallow() def fail_on_shallow(wd: GitWorkdir) -> None: """experimental, may change at any time""" if wd.is_shallow(): raise ValueError( f'{wd.path} is shallow, please correct with "git fetch --unshallow"' ) def fail_on_missing_submodules(wd: GitWorkdir) -> None: """ Fail if submodules are defined but not initialized/cloned. This pre_parse function checks if there are submodules defined in .gitmodules but not properly initialized (cloned). This helps prevent packaging incomplete projects when submodules are required for a complete build. """ gitmodules_path = wd.path / ".gitmodules" if not gitmodules_path.exists(): # No submodules defined, nothing to check return # Get submodule status - lines starting with '-' indicate uninitialized submodules status_result = run_git(["submodule", "status"], wd.path) if status_result.returncode != 0: # Command failed, might not be in a git repo or other error log.debug("Failed to check submodule status: %s", status_result.stderr) return status_lines = ( status_result.stdout.strip().split("\n") if status_result.stdout.strip() else [] ) uninitialized_submodules = [] for line in status_lines: line = line.strip() if line.startswith("-"): # Extract submodule path (everything after the commit hash) parts = line.split() if len(parts) >= 2: submodule_path = parts[1] uninitialized_submodules.append(submodule_path) # If .gitmodules exists but git submodule status returns nothing, # it means submodules are defined but not properly set up (common after cloning without --recurse-submodules) if not status_lines and gitmodules_path.exists(): raise ValueError( f"Submodules are defined in .gitmodules but not initialized in {wd.path}. " f"Please run 'git submodule update --init --recursive' to initialize them." ) if uninitialized_submodules: submodule_list = ", ".join(uninitialized_submodules) raise ValueError( f"Submodules are not initialized in {wd.path}: {submodule_list}. " f"Please run 'git submodule update --init --recursive' to initialize them." ) # Mapping from enum items to actual pre_parse functions _GIT_PRE_PARSE_FUNCTIONS: dict[GitPreParse, Callable[[GitWorkdir], None]] = { GitPreParse.WARN_ON_SHALLOW: warn_on_shallow, GitPreParse.FAIL_ON_SHALLOW: fail_on_shallow, GitPreParse.FETCH_ON_SHALLOW: fetch_on_shallow, GitPreParse.FAIL_ON_MISSING_SUBMODULES: fail_on_missing_submodules, } def get_working_directory(config: Configuration, root: _t.PathT) -> GitWorkdir | None: """ Return the working directory (``GitWorkdir``). """ if config.parent: # todo broken return GitWorkdir.from_potential_worktree(config.parent) for potential_root in discover.walk_potential_roots( root, search_parents=config.search_parent_directories ): potential_wd = GitWorkdir.from_potential_worktree(potential_root) if potential_wd is not None: return potential_wd return GitWorkdir.from_potential_worktree(root) def parse( root: _t.PathT, config: Configuration, describe_command: str | list[str] | None = None, pre_parse: Callable[[GitWorkdir], None] | None = None, ) -> ScmVersion | None: """ :param pre_parse: experimental pre_parse action, may change at any time. Takes precedence over config.git_pre_parse if provided. """ _require_command("git") wd = get_working_directory(config, root) if wd: # Use function parameter first, then config setting, then default if pre_parse is not None: effective_pre_parse = pre_parse else: # config.scm.git.pre_parse is always a GitPreParse enum instance effective_pre_parse = _GIT_PRE_PARSE_FUNCTIONS.get( config.scm.git.pre_parse, warn_on_shallow ) return _git_parse_inner( config, wd, describe_command=describe_command, pre_parse=effective_pre_parse ) else: return None def version_from_describe( wd: GitWorkdir | hg_git.GitWorkdirHgClient, config: Configuration, describe_command: _t.CMD_TYPE | None, ) -> ScmVersion | None: if config.scm.git.describe_command is not None: describe_command = config.scm.git.describe_command if describe_command is not None: if isinstance(describe_command, str): describe_command = shlex.split(describe_command) # todo: figure how to ensure git with gitdir gets correctly invoked if describe_command[0] == "git": describe_res = run_git(describe_command[1:], wd.path) else: describe_res = _run(describe_command, wd.path) else: describe_res = wd.default_describe() def parse_describe(output: str) -> ScmVersion: tag, distance, node, dirty = _git_parse_describe(output) return meta(tag=tag, distance=distance, dirty=dirty, node=node, config=config) return describe_res.parse_success(parse=parse_describe) def _git_parse_inner( config: Configuration, wd: GitWorkdir | hg_git.GitWorkdirHgClient, pre_parse: (Callable[[GitWorkdir | hg_git.GitWorkdirHgClient], None]) | None = None, describe_command: _t.CMD_TYPE | None = None, ) -> ScmVersion: if pre_parse: pre_parse(wd) version = version_from_describe(wd, config, describe_command) if version is None: # If 'git git_describe_command' failed, try to get the information otherwise. tag = config.version_cls(config.fallback_version or "0.0") node = wd.node() if node is None: distance = 0 dirty = True else: distance = wd.count_all_nodes() node = "g" + node dirty = wd.is_dirty() version = meta( tag=tag, distance=distance, dirty=dirty, node=node, config=config ) branch = wd.get_branch() node_date = wd.get_head_date() # If we can't get node_date from HEAD (e.g., no commits yet), # and the working directory is dirty, try to use the latest # modification time of changed files instead of current time if node_date is None and wd.is_dirty(): dirty_date = wd.get_dirty_tag_date() if dirty_date is not None: node_date = dirty_date # Final fallback to current time if node_date is None: node_date = datetime.now(timezone.utc).date() return dataclasses.replace(version, branch=branch, node_date=node_date) def _git_parse_describe( describe_output: str, ) -> tuple[str, int, str | None, bool]: # 'describe_output' looks e.g. like 'v1.5.0-0-g4060507' or # 'v1.15.1rc1-37-g9bd1298-dirty'. # It may also just be a bare tag name if this is a tagged commit and we are # parsing a .git_archival.txt file. if describe_output.endswith("-dirty"): dirty = True describe_output = describe_output[:-6] else: dirty = False split = describe_output.rsplit("-", 2) if len(split) < 3: # probably a tagged commit tag = describe_output number = 0 node = None else: tag, number_, node = split number = int(number_) return tag, number, node, dirty def archival_to_version( data: dict[str, str], config: Configuration ) -> ScmVersion | None: node: str | None log.debug("data %s", data) archival_describe = data.get("describe-name", DESCRIBE_UNSUPPORTED) if DESCRIBE_UNSUPPORTED in archival_describe: warnings.warn("git archive did not support describe output") else: tag, number, node, _ = _git_parse_describe(archival_describe) return meta( tag, config=config, distance=number, node=node, ) for ref in REF_TAG_RE.findall(data.get("ref-names", "")): version = tag_to_version(ref, config) if version is not None: return meta(version, config=config) node = data.get("node") if node is None: return None elif "$FORMAT" in node.upper(): warnings.warn("unprocessed git archival found (no export subst applied)") return None else: return meta("0.0", node=node, config=config) def parse_archival(root: _t.PathT, config: Configuration) -> ScmVersion | None: archival = os.path.join(root, ".git_archival.txt") data = data_from_mime(archival) return archival_to_version(data, config=config) setuptools-scm-9.2.2/src/setuptools_scm/hg.py000066400000000000000000000247551507525030000213610ustar00rootroot00000000000000from __future__ import annotations import datetime import logging import os from pathlib import Path from typing import TYPE_CHECKING from typing import Any from . import Configuration from ._version_cls import Version from .integration import data_from_mime from .scm_workdir import Workdir from .scm_workdir import get_latest_file_mtime from .version import ScmVersion from .version import meta from .version import tag_to_version if TYPE_CHECKING: from . import _types as _t from ._run_cmd import CompletedProcess from ._run_cmd import require_command as _require_command from ._run_cmd import run as _run log = logging.getLogger(__name__) def _get_hg_command() -> str: """Get the hg command from environment, allowing runtime configuration.""" return os.environ.get("SETUPTOOLS_SCM_HG_COMMAND", "hg") def run_hg(args: list[str], cwd: _t.PathT, **kwargs: Any) -> CompletedProcess: """Run mercurial command with the configured hg executable.""" cmd = [_get_hg_command(), *args] return _run(cmd, cwd=cwd, **kwargs) class HgWorkdir(Workdir): @classmethod def from_potential_worktree(cls, wd: _t.PathT) -> HgWorkdir | None: res = run_hg(["root"], wd) if res.returncode: return None return cls(Path(res.stdout)) def get_meta(self, config: Configuration) -> ScmVersion | None: # TODO: support bookmarks and topics (but nowadays bookmarks are # mainly used to emulate Git branches, which is already supported with # the dedicated class GitWorkdirHgClient) node_info = self._get_node_info() if node_info is None: return None node, tags_str, node_date_str = node_info branch_info = self._get_branch_info() branch, dirty, dirty_date = branch_info # Determine the appropriate node date node_date = self._get_node_date(dirty, node_date_str, dirty_date) # Handle initial/empty repository if self._is_initial_node(node): return self._create_initial_meta(config, dirty, branch, node_date) node = "h" + node tags = self._parse_tags(tags_str) # Try to get version from current tags tag_version = self._get_version_from_tags(tags, config) if tag_version: return meta(tag_version, dirty=dirty, branch=branch, config=config) # Fall back to distance-based versioning return self._get_distance_based_version(config, dirty, branch, node, node_date) def _get_node_info(self) -> tuple[str, str, str] | None: """Get node, tags, and date information from mercurial log.""" try: node, tags_str, node_date_str = self.hg_log( ".", "{node}\n{tag}\n{date|shortdate}" ).split("\n") return node, tags_str, node_date_str except ValueError: log.exception("Failed to get node info") return None def _get_branch_info(self) -> tuple[str, bool, str]: """Get branch name, dirty status, and dirty date.""" branch, dirty_str, dirty_date = run_hg( ["id", "-T", "{branch}\n{if(dirty, 1, 0)}\n{date|shortdate}"], cwd=self.path, check=True, ).stdout.split("\n") dirty = bool(int(dirty_str)) return branch, dirty, dirty_date def _get_node_date( self, dirty: bool, node_date_str: str, dirty_date: str ) -> datetime.date: """Get the appropriate node date, preferring file modification times for dirty repos.""" if dirty: file_mod_date = self.get_dirty_tag_date() if file_mod_date is not None: return file_mod_date # Fall back to hg id date for dirty repos return datetime.date.fromisoformat(dirty_date) else: return datetime.date.fromisoformat(node_date_str) def _is_initial_node(self, node: str) -> bool: """Check if this is an initial/empty repository node.""" return node == "0" * len(node) def _create_initial_meta( self, config: Configuration, dirty: bool, branch: str, node_date: datetime.date ) -> ScmVersion: """Create metadata for initial/empty repository.""" log.debug("initial node %s", self.path) return meta( Version("0.0"), config=config, dirty=dirty, branch=branch, node_date=node_date, ) def _parse_tags(self, tags_str: str) -> list[str]: """Parse and filter tags from mercurial output.""" tags = tags_str.split() if "tip" in tags: # tip is not a real tag tags.remove("tip") return tags def _get_version_from_tags( self, tags: list[str], config: Configuration ) -> Version | None: """Try to get a version from the current tags.""" if tags: tag = tag_to_version(tags[0], config) return tag return None def _get_distance_based_version( self, config: Configuration, dirty: bool, branch: str, node: str, node_date: datetime.date, ) -> ScmVersion | None: """Get version based on distance from latest tag.""" try: tag_str = self.get_latest_normalizable_tag() if tag_str is None: dist = self.get_distance_revs("") else: dist = self.get_distance_revs(tag_str) if tag_str == "null" or tag_str is None: tag = Version("0.0") dist += 1 else: maybe_tag = tag_to_version(tag_str, config=config) if maybe_tag is None: # If tag conversion fails, treat as no tag found tag = Version("0.0") dist += 1 else: tag = maybe_tag if self.check_changes_since_tag(tag_str) or dirty: return meta( tag, distance=dist, node=node, dirty=dirty, branch=branch, config=config, node_date=node_date, ) else: return meta(tag, config=config, node_date=node_date) except ValueError: # unpacking failed, old hg log.exception("error") return None def hg_log(self, revset: str, template: str) -> str: return run_hg( ["log", "-r", revset, "-T", template], cwd=self.path, check=True ).stdout def get_latest_normalizable_tag(self) -> str | None: # Gets all tags containing a '.' (see #229) from oldest to newest outlines = self.hg_log( revset="ancestors(.) and tag('re:\\.')", template="{tags}{if(tags, '\n', '')}", ).split() if not outlines: return None tag = outlines[-1].split()[-1] return tag def get_distance_revs(self, rev1: str, rev2: str = ".") -> int: revset = f"({rev1}::{rev2})" out = self.hg_log(revset, ".") return len(out) - 1 def check_changes_since_tag(self, tag: str | None) -> bool: if tag == "0.0" or tag is None: return True revset = ( "(branch(.)" # look for revisions in this branch only f" and tag({tag!r})::." # after the last tag # ignore commits that only modify .hgtags and nothing else: " and (merge() or file('re:^(?!\\.hgtags).*$'))" f" and not tag({tag!r}))" # ignore the tagged commit itself ) return bool(self.hg_log(revset, ".")) def get_dirty_tag_date(self) -> datetime.date | None: """Get the latest modification time of changed files in the working directory. Returns the date of the most recently modified file that has changes, or None if no files are changed or if an error occurs. """ try: # Check if working directory is dirty first res = run_hg(["id", "-T", "{dirty}"], cwd=self.path) if res.returncode != 0 or not bool(res.stdout): return None # Get list of changed files using hg status status_res = run_hg(["status", "-m", "-a", "-r"], cwd=self.path) if status_res.returncode != 0: return None changed_files = [] for line in status_res.stdout.strip().split("\n"): if line and len(line) > 2: # Format is "M filename" or "A filename" etc. filepath = line[2:] # Skip status char and space changed_files.append(filepath) return get_latest_file_mtime(changed_files, self.path) except Exception as e: log.debug("Failed to get dirty tag date: %s", e) return None def parse(root: _t.PathT, config: Configuration) -> ScmVersion | None: hg_cmd = _get_hg_command() _require_command(hg_cmd) if os.path.exists(os.path.join(root, ".hg/git")): res = run_hg(["path"], root) if not res.returncode: for line in res.stdout.split("\n"): if line.startswith("default ="): path = Path(line.split()[2]) if path.name.endswith(".git") or (path / ".git").exists(): from .git import _git_parse_inner from .hg_git import GitWorkdirHgClient wd_hggit = GitWorkdirHgClient.from_potential_worktree(root) if wd_hggit: return _git_parse_inner(config, wd_hggit) wd = HgWorkdir.from_potential_worktree(config.absolute_root) if wd is None: return None return wd.get_meta(config) def archival_to_version(data: dict[str, str], config: Configuration) -> ScmVersion: log.debug("data %s", data) node = data.get("node", "") if node: node = "h" + node if "tag" in data: return meta(data["tag"], config=config) elif "latesttag" in data: return meta( data["latesttag"], distance=int(data["latesttagdistance"]), node=node, branch=data.get("branch"), config=config, ) else: return meta(config.version_cls("0.0"), node=node, config=config) def parse_archival(root: _t.PathT, config: Configuration) -> ScmVersion: archival = os.path.join(root, ".hg_archival.txt") data = data_from_mime(archival) return archival_to_version(data, config=config) setuptools-scm-9.2.2/src/setuptools_scm/hg_git.py000066400000000000000000000126441507525030000222160ustar00rootroot00000000000000from __future__ import annotations import logging import os from contextlib import suppress from datetime import date from pathlib import Path from . import _types as _t from ._run_cmd import CompletedProcess as _CompletedProcess from .git import GitWorkdir from .hg import HgWorkdir from .hg import run_hg from .scm_workdir import get_latest_file_mtime log = logging.getLogger(__name__) _FAKE_GIT_DESCRIBE_ERROR = _CompletedProcess( "fake git describe output for hg", 1, "<>hg git failed to describe", ) class GitWorkdirHgClient(GitWorkdir, HgWorkdir): @classmethod def from_potential_worktree(cls, wd: _t.PathT) -> GitWorkdirHgClient | None: res = run_hg(["root"], cwd=wd).parse_success(parse=Path) if res is None: return None return cls(res) def is_dirty(self) -> bool: res = run_hg(["id", "-T", "{dirty}"], cwd=self.path, check=True) return bool(res.stdout) def get_branch(self) -> str | None: res = run_hg(["id", "-T", "{bookmarks}"], cwd=self.path) if res.returncode: log.info("branch err %s", res) return None return res.stdout def get_head_date(self) -> date | None: return run_hg( ["log", "-r", ".", "-T", "{shortdate(date)}"], cwd=self.path ).parse_success(parse=date.fromisoformat, error_msg="head date err") def get_dirty_tag_date(self) -> date | None: """Get the latest modification time of changed files in the working directory. Returns the date of the most recently modified file that has changes, or None if no files are changed or if an error occurs. """ if not self.is_dirty(): return None try: # Get list of changed files using hg status status_res = run_hg(["status", "-m", "-a", "-r"], cwd=self.path) if status_res.returncode != 0: return None changed_files = [] for line in status_res.stdout.strip().split("\n"): if line and len(line) > 2: # Format is "M filename" or "A filename" etc. filepath = line[2:] # Skip status char and space changed_files.append(filepath) return get_latest_file_mtime(changed_files, self.path) except Exception as e: log.debug("Failed to get dirty tag date: %s", e) return None def is_shallow(self) -> bool: return False def fetch_shallow(self) -> None: pass def get_hg_node(self) -> str | None: res = run_hg(["log", "-r", ".", "-T", "{node}"], cwd=self.path) if res.returncode: return None else: return res.stdout def _hg2git(self, hg_node: str) -> str | None: with suppress(FileNotFoundError): with open(os.path.join(self.path, ".hg/git-mapfile")) as map_items: for item in map_items: if hg_node in item: git_node, hg_node = item.split() return git_node return None def node(self) -> str | None: hg_node = self.get_hg_node() if hg_node is None: return None git_node = self._hg2git(hg_node) if git_node is None: # trying again after hg -> git run_hg(["gexport"], cwd=self.path) git_node = self._hg2git(hg_node) if git_node is None: log.debug("Cannot get git node so we use hg node %s", hg_node) if hg_node == "0" * len(hg_node): # mimic Git behavior return None return hg_node return git_node def count_all_nodes(self) -> int: res = run_hg(["log", "-r", "ancestors(.)", "-T", "."], cwd=self.path) return len(res.stdout) def default_describe(self) -> _CompletedProcess: """ Tentative to reproduce the output of `git describe --dirty --tags --long --match *[0-9]*` """ res = run_hg( [ "log", "-r", "(reverse(ancestors(.)) and tag(r're:v?[0-9].*'))", "-T", "{tags}{if(tags, ' ', '')}", ], cwd=self.path, ) if res.returncode: return _FAKE_GIT_DESCRIBE_ERROR hg_tags: list[str] = res.stdout.split() if not hg_tags: return _FAKE_GIT_DESCRIBE_ERROR with self.path.joinpath(".hg/git-tags").open() as fp: git_tags: dict[str, str] = dict(line.split()[::-1] for line in fp) tag: str for hg_tag in hg_tags: if hg_tag in git_tags: tag = hg_tag break else: logging.warning("tag not found hg=%s git=%s", hg_tags, git_tags) return _FAKE_GIT_DESCRIBE_ERROR res = run_hg(["log", "-r", f"'{tag}'::.", "-T", "."], cwd=self.path) if res.returncode: return _FAKE_GIT_DESCRIBE_ERROR distance = len(res.stdout) - 1 node = self.node() assert node is not None desc = f"{tag}-{distance}-g{node}" if self.is_dirty(): desc += "-dirty" log.debug("faked describe %r", desc) return _CompletedProcess( ["setuptools-scm", "faked", "describe"], returncode=0, stdout=desc, stderr="", ) setuptools-scm-9.2.2/src/setuptools_scm/integration.py000066400000000000000000000014461507525030000232760ustar00rootroot00000000000000from __future__ import annotations import logging import textwrap from pathlib import Path from . import _types as _t log = logging.getLogger(__name__) def data_from_mime(path: _t.PathT, content: str | None = None) -> dict[str, str]: """return a mapping from mime/pseudo-mime content :param path: path to the mime file :param content: content of the mime file, if None, read from path :rtype: dict[str, str] """ if content is None: content = Path(path).read_text(encoding="utf-8") log.debug("mime %s content:\n%s", path, textwrap.indent(content, " ")) from email.parser import HeaderParser parser = HeaderParser() message = parser.parsestr(content) data = dict(message.items()) log.debug("mime %s data:\n%s", path, data) return data setuptools-scm-9.2.2/src/setuptools_scm/py.typed000066400000000000000000000000001507525030000220610ustar00rootroot00000000000000setuptools-scm-9.2.2/src/setuptools_scm/scm_workdir.py000066400000000000000000000026721507525030000233000ustar00rootroot00000000000000from __future__ import annotations import logging from dataclasses import dataclass from datetime import date from datetime import datetime from datetime import timezone from pathlib import Path from ._config import Configuration from .version import ScmVersion log = logging.getLogger(__name__) def get_latest_file_mtime(changed_files: list[str], base_path: Path) -> date | None: """Get the latest modification time of the given files. Args: changed_files: List of relative file paths base_path: Base directory path to resolve relative paths Returns: The date of the most recently modified file, or None if no valid files found """ if not changed_files or changed_files == [""]: return None latest_mtime = 0.0 for filepath in changed_files: full_path = base_path / filepath try: file_stat = full_path.stat() latest_mtime = max(latest_mtime, file_stat.st_mtime) except OSError: # File might not exist or be accessible, skip it log.debug("Failed to get mtime for %s", full_path) continue if latest_mtime > 0: # Convert to UTC date dt = datetime.fromtimestamp(latest_mtime, timezone.utc) return dt.date() return None @dataclass() class Workdir: path: Path def run_describe(self, config: Configuration) -> ScmVersion: raise NotImplementedError(self.run_describe) setuptools-scm-9.2.2/src/setuptools_scm/version.py000066400000000000000000000460221507525030000224370ustar00rootroot00000000000000from __future__ import annotations import dataclasses import logging import os import re import warnings from datetime import date from datetime import datetime from datetime import timezone from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import Match from . import _entrypoints from . import _modify_version from ._node_utils import _format_node_for_output if TYPE_CHECKING: import sys if sys.version_info >= (3, 10): from typing import Concatenate from typing import ParamSpec else: from typing_extensions import Concatenate from typing_extensions import ParamSpec _P = ParamSpec("_P") from typing import TypedDict from . import _config from . import _version_cls as _v from ._version_cls import Version as PkgVersion from ._version_cls import _VersionT log = logging.getLogger(__name__) SEMVER_MINOR = 2 SEMVER_PATCH = 3 SEMVER_LEN = 3 class _TagDict(TypedDict): version: str prefix: str suffix: str def _parse_version_tag( tag: str | object, config: _config.Configuration ) -> _TagDict | None: match = config.tag_regex.match(str(tag)) if match: key: str | int = 1 if len(match.groups()) == 1 else "version" full = match.group(0) log.debug("%r %r %s", tag, config.tag_regex, match) log.debug( "key %s data %s, %s, %r", key, match.groupdict(), match.groups(), full ) if version := match.group(key): result = _TagDict( version=version, prefix=full[: match.start(key)], suffix=full[match.end(key) :], ) log.debug("tag %r parsed to %r", tag, result) return result raise ValueError( f'The tag_regex "{config.tag_regex.pattern}" matched tag "{tag}", ' "however the matched group has no value." ) else: log.debug("tag %r did not parse", tag) return None def callable_or_entrypoint(group: str, callable_or_name: str | Any) -> Any: log.debug("ep %r %r", group, callable_or_name) if callable(callable_or_name): return callable_or_name from ._entrypoints import _get_ep return _get_ep(group, callable_or_name) def tag_to_version( tag: _VersionT | str, config: _config.Configuration ) -> _VersionT | None: """ take a tag that might be prefixed with a keyword and return only the version part """ log.debug("tag %s", tag) tag_dict = _parse_version_tag(tag, config) if tag_dict is None or not tag_dict.get("version", None): warnings.warn(f"tag {tag!r} no version found") return None version_str = tag_dict["version"] log.debug("version pre parse %s", version_str) # Try to create version from base version first try: version: _VersionT = config.version_cls(version_str) log.debug("version=%r", version) except Exception: warnings.warn( f"tag {tag!r} will be stripped of its suffix {tag_dict.get('suffix', '')!r}" ) # Fall back to trying without any suffix version = config.version_cls(version_str) log.debug("version=%r", version) return version # If base version is valid, check if we can preserve the suffix if suffix := tag_dict.get("suffix", ""): log.debug("tag %r includes local build data %r, preserving it", tag, suffix) # Try creating version with suffix - if it fails, we'll use the base version try: version_with_suffix = config.version_cls(version_str + suffix) log.debug("version with suffix=%r", version_with_suffix) return version_with_suffix except Exception: warnings.warn(f"tag {tag!r} will be stripped of its suffix {suffix!r}") # Return the base version without suffix return version return version def _source_epoch_or_utc_now() -> datetime: if "SOURCE_DATE_EPOCH" in os.environ: date_epoch = int(os.environ["SOURCE_DATE_EPOCH"]) return datetime.fromtimestamp(date_epoch, timezone.utc) else: return datetime.now(timezone.utc) @dataclasses.dataclass class ScmVersion: """represents a parsed version from scm""" tag: _v.Version | _v.NonNormalizedVersion """the related tag or preformatted version""" config: _config.Configuration """the configuration used to parse the version""" distance: int = 0 """the number of commits since the tag""" node: str | None = None """the shortened node id""" dirty: bool = False """whether the working copy had uncommitted changes""" preformatted: bool = False """whether the version string was preformatted""" branch: str | None = None """the branch name if any""" node_date: date | None = None """the date of the commit if available""" time: datetime = dataclasses.field(default_factory=_source_epoch_or_utc_now) """the current time or source epoch time only set for unit-testing version schemes for real usage it must be `now(utc)` or `SOURCE_EPOCH` """ @property def exact(self) -> bool: """returns true checked out exactly on a tag and no local changes apply""" return self.distance == 0 and not self.dirty @property def short_node(self) -> str | None: """Return the node formatted for output.""" return _format_node_for_output(self.node) def __repr__(self) -> str: return ( f"" ) def format_with(self, fmt: str, **kw: object) -> str: """format a given format string with attributes of this object""" return fmt.format( time=self.time, tag=self.tag, distance=self.distance, node=_format_node_for_output(self.node), dirty=self.dirty, branch=self.branch, node_date=self.node_date, **kw, ) def format_choice(self, clean_format: str, dirty_format: str, **kw: object) -> str: """given `clean_format` and `dirty_format` choose one based on `self.dirty` and format it using `self.format_with`""" return self.format_with(dirty_format if self.dirty else clean_format, **kw) def format_next_version( self, guess_next: Callable[Concatenate[ScmVersion, _P], str], fmt: str = "{guessed}.dev{distance}", *k: _P.args, **kw: _P.kwargs, ) -> str: guessed = guess_next(self, *k, **kw) return self.format_with(fmt, guessed=guessed) def _parse_tag( tag: _VersionT | str, preformatted: bool, config: _config.Configuration ) -> _VersionT: if preformatted: # For preformatted versions, tag should already be validated as a version object # String validation is handled in meta function before calling this if isinstance(tag, str): # This should not happen with enhanced meta, but kept for safety return _v.NonNormalizedVersion(tag) else: # Already a version object (including test mocks), return as-is return tag elif not isinstance(tag, config.version_cls): version = tag_to_version(tag, config) assert version is not None return version else: return tag def meta( tag: str | _VersionT, *, distance: int = 0, dirty: bool = False, node: str | None = None, preformatted: bool = False, branch: str | None = None, config: _config.Configuration, node_date: date | None = None, time: datetime | None = None, ) -> ScmVersion: parsed_version: _VersionT # Enhanced string validation for preformatted versions if preformatted and isinstance(tag, str): # Validate PEP 440 compliance using NonNormalizedVersion # Let validation errors bubble up to the caller parsed_version = _v.NonNormalizedVersion(tag) else: # Use existing _parse_tag logic for non-preformatted or already validated inputs parsed_version = _parse_tag(tag, preformatted, config) log.info("version %s -> %s", tag, parsed_version) assert parsed_version is not None, f"Can't parse version {tag}" scm_version = ScmVersion( parsed_version, distance=distance, node=node, dirty=dirty, preformatted=preformatted, branch=branch, config=config, node_date=node_date, ) if time is not None: scm_version = dataclasses.replace(scm_version, time=time) return scm_version def guess_next_version(tag_version: ScmVersion) -> str: version = _modify_version.strip_local(str(tag_version.tag)) return _modify_version._bump_dev(version) or _modify_version._bump_regex(version) def guess_next_dev_version(version: ScmVersion) -> str: if version.exact: return version.format_with("{tag}") else: return version.format_next_version(guess_next_version) def guess_next_simple_semver( version: ScmVersion, retain: int, increment: bool = True ) -> str: if isinstance(version.tag, _v.Version): parts = list(version.tag.release[:retain]) else: try: parts = [int(i) for i in str(version.tag).split(".")[:retain]] except ValueError: raise ValueError(f"{version} can't be parsed as numeric version") from None while len(parts) < retain: parts.append(0) if increment: parts[-1] += 1 while len(parts) < SEMVER_LEN: parts.append(0) return ".".join(str(i) for i in parts) def simplified_semver_version(version: ScmVersion) -> str: if version.exact: return guess_next_simple_semver(version, retain=SEMVER_LEN, increment=False) elif version.branch is not None and "feature" in version.branch: return version.format_next_version( guess_next_simple_semver, retain=SEMVER_MINOR ) else: return version.format_next_version( guess_next_simple_semver, retain=SEMVER_PATCH ) def release_branch_semver_version(version: ScmVersion) -> str: if version.exact: return version.format_with("{tag}") if version.branch is not None: # Does the branch name (stripped of namespace) parse as a version? branch_ver_data = _parse_version_tag( version.branch.split("/")[-1], version.config ) if branch_ver_data is not None: branch_ver = branch_ver_data["version"] if branch_ver[0] == "v": # Allow branches that start with 'v', similar to Version. branch_ver = branch_ver[1:] # Does the branch version up to the minor part match the tag? If not it # might be like, an issue number or something and not a version number, so # we only want to use it if it matches. tag_ver_up_to_minor = str(version.tag).split(".")[:SEMVER_MINOR] branch_ver_up_to_minor = branch_ver.split(".")[:SEMVER_MINOR] if branch_ver_up_to_minor == tag_ver_up_to_minor: # We're in a release/maintenance branch, next is a patch/rc/beta bump: return version.format_next_version(guess_next_version) # We're in a development branch, next is a minor bump: return version.format_next_version(guess_next_simple_semver, retain=SEMVER_MINOR) def release_branch_semver(version: ScmVersion) -> str: warnings.warn( "release_branch_semver is deprecated and will be removed in the future. " "Use release_branch_semver_version instead", category=DeprecationWarning, stacklevel=2, ) return release_branch_semver_version(version) def only_version(version: ScmVersion) -> str: return version.format_with("{tag}") def no_guess_dev_version(version: ScmVersion) -> str: if version.exact: return version.format_with("{tag}") else: return version.format_next_version(_modify_version._dont_guess_next_version) _DATE_REGEX = re.compile( r""" ^(?P (?P[vV]?) (?P\d{2}|\d{4})(?:\.\d{1,2}){2}) (?:\.(?P\d*))?$ """, re.VERBOSE, ) def date_ver_match(ver: str) -> Match[str] | None: return _DATE_REGEX.match(ver) def guess_next_date_ver( version: ScmVersion, node_date: date | None = None, date_fmt: str | None = None, version_cls: type | None = None, ) -> str: """ same-day -> patch +1 other-day -> today distance is always added as .devX """ match = date_ver_match(str(version.tag)) if match is None: warnings.warn( f"{version} does not correspond to a valid versioning date, " "assuming legacy version" ) if date_fmt is None: date_fmt = "%y.%m.%d" else: # deduct date format if not provided if date_fmt is None: date_fmt = "%Y.%m.%d" if len(match.group("year")) == 4 else "%y.%m.%d" if prefix := match.group("prefix"): if not date_fmt.startswith(prefix): date_fmt = prefix + date_fmt today = version.time.date() head_date = node_date or today # compute patch if match is None: # For legacy non-date tags, always use patch=0 (treat as "other day") # Use yesterday to ensure tag_date != head_date from datetime import timedelta tag_date = head_date - timedelta(days=1) else: tag_date = ( datetime.strptime(match.group("date"), date_fmt) .replace(tzinfo=timezone.utc) .date() ) if tag_date == head_date: assert match is not None # Same day as existing date tag - increment patch patch = int(match.group("patch") or "0") + 1 else: # Different day or legacy non-date tag - use patch 0 if tag_date > head_date and match is not None: # warn on future times (only for actual date tags, not legacy) warnings.warn( f"your previous tag ({tag_date}) is ahead your node date ({head_date})" ) patch = 0 next_version = "{node_date:{date_fmt}}.{patch}".format( node_date=head_date, date_fmt=date_fmt, patch=patch ) # rely on the Version object to ensure consistency (e.g. remove leading 0s) if version_cls is None: version_cls = PkgVersion next_version = str(version_cls(next_version)) return next_version def calver_by_date(version: ScmVersion) -> str: if version.exact and not version.dirty: return version.format_with("{tag}") # TODO: move the release-X check to a new scheme if version.branch is not None and version.branch.startswith("release-"): branch_ver = _parse_version_tag(version.branch.split("-")[-1], version.config) if branch_ver is not None: ver = branch_ver["version"] match = date_ver_match(ver) if match: return ver return version.format_next_version( guess_next_date_ver, node_date=version.node_date, version_cls=version.config.version_cls, ) def get_local_node_and_date(version: ScmVersion) -> str: return _modify_version._format_local_with_time(version, time_format="%Y%m%d") def get_local_node_and_timestamp(version: ScmVersion) -> str: return _modify_version._format_local_with_time(version, time_format="%Y%m%d%H%M%S") def get_local_dirty_tag(version: ScmVersion) -> str: return version.format_choice("", "+dirty") def get_no_local_node(version: ScmVersion) -> str: return "" def postrelease_version(version: ScmVersion) -> str: if version.exact: return version.format_with("{tag}") else: return version.format_with("{tag}.post{distance}") def _combine_version_with_local_parts( main_version: str, *local_parts: str | None ) -> str: """ Combine a main version with multiple local parts into a valid PEP 440 version string. Handles deduplication of local parts to avoid adding the same local data twice. Args: main_version: The main version string (e.g., "1.2.0", "1.2.dev3") *local_parts: Variable number of local version parts, can be None or empty Returns: A valid PEP 440 version string Examples: _combine_version_with_local_parts("1.2.0", "build.123", "d20090213") -> "1.2.0+build.123.d20090213" _combine_version_with_local_parts("1.2.0", "build.123", None) -> "1.2.0+build.123" _combine_version_with_local_parts("1.2.0+build.123", "d20090213") -> "1.2.0+build.123.d20090213" _combine_version_with_local_parts("1.2.0+build.123", "build.123") -> "1.2.0+build.123" # no duplication _combine_version_with_local_parts("1.2.0", None, None) -> "1.2.0" """ # Split main version into base and existing local parts if "+" in main_version: main_part, existing_local = main_version.split("+", 1) all_local_parts = existing_local.split(".") else: main_part = main_version all_local_parts = [] # Process each new local part for part in local_parts: if not part or not part.strip(): continue # Strip any leading + and split into segments clean_part = part.strip("+") if not clean_part: continue # Split multi-part local identifiers (e.g., "build.123" -> ["build", "123"]) part_segments = clean_part.split(".") # Add each segment if not already present for segment in part_segments: if segment and segment not in all_local_parts: all_local_parts.append(segment) # Return combined result if all_local_parts: return main_part + "+" + ".".join(all_local_parts) else: return main_part def format_version(version: ScmVersion) -> str: log.debug("scm version %s", version) log.debug("config %s", version.config) if version.preformatted: return str(version.tag) # Extract original tag's local data for later combination original_local = "" if hasattr(version.tag, "local") and version.tag.local is not None: original_local = str(version.tag.local) # Create a patched ScmVersion with only the base version (no local data) for version schemes from dataclasses import replace # Extract the base version (public part) from the tag using config's version_cls base_version_str = str(version.tag.public) base_tag = version.config.version_cls(base_version_str) version_for_scheme = replace(version, tag=base_tag) main_version = _entrypoints._call_version_scheme( version_for_scheme, "setuptools_scm.version_scheme", version.config.version_scheme, ) log.debug("version %s", main_version) assert main_version is not None local_version = _entrypoints._call_version_scheme( version, "setuptools_scm.local_scheme", version.config.local_scheme, "+unknown" ) log.debug("local_version %s", local_version) # Combine main version with original local data and new local scheme data return _combine_version_with_local_parts( str(main_version), original_local, local_version ) setuptools-scm-9.2.2/testing/000077500000000000000000000000001507525030000161775ustar00rootroot00000000000000setuptools-scm-9.2.2/testing/Dockerfile.busted-buster000066400000000000000000000002771507525030000227660ustar00rootroot00000000000000FROM debian:buster RUN apt-get update -q && apt-get install -yq python3-pip python3-setuptools RUN printf "[easy_install]\nallow_hosts=localhost\nfind_links=/dist\n" > /root/.pydistutils.cfg setuptools-scm-9.2.2/testing/Dockerfile.rawhide-git000066400000000000000000000003251507525030000223740ustar00rootroot00000000000000FROM registry.fedoraproject.org/fedora:rawhide RUN dnf install git -y RUN git --version USER 1000:1000 VOLUME /repo WORKDIR /repo ENTRYPOINT mkdir git-archived && git archive HEAD -o git-archived/archival.tar.gz setuptools-scm-9.2.2/testing/INTEGRATION_MIGRATION_PLAN.md000066400000000000000000000112231507525030000225260ustar00rootroot00000000000000## Setuptools integration test migration plan Purpose: streamline/simplify integration codepaths and make tests faster and easier to write by preferring unit-level inference over setuptools-driven E2E where possible. Reference helper for unit tests: ```python from setuptools_scm._integration.pyproject_reading import PyProjectData from setuptools_scm._integration.version_inference import infer_version_string version = infer_version_string( dist_name="pkg", pyproject_data=PyProjectData.for_testing(project_present=True, section_present=True, project_name="pkg"), overrides={"fallback_version": "1.2.3"}, ) ``` ### Completed - [x] Introduced `infer_version_string` pure helper to compute versions without a `Distribution` or `setup.py`. ### Migration candidates (replace E2E/Distribution-hook tests with unit inference) - [ ] `testing/test_integration.py::test_pyproject_support` - Proposed unit: `test_infer_fallback_version_from_pyproject` - Notes: Use `PyProjectData.for_testing(..., section_present=True, project_present=True)` + overrides `{fallback_version: "12.34"}`. - [ ] `testing/test_integration.py::test_setuptools_version_keyword_ensures_regex` - Proposed unit: `test_infer_tag_regex_from_overrides` - Notes: Create repo/tag in `wd`, call `infer_version_string(..., overrides={"tag_regex": "(1.0)"})`. - [ ] `testing/test_basic_api.py::test_parentdir_prefix` - Proposed unit: `test_infer_parentdir_prefix_version` - Notes: Use directory name prefix and `{parentdir_prefix_version: "projectname-"}`. - [ ] `testing/test_basic_api.py::test_fallback` - Proposed unit: `test_infer_fallback_version` - Notes: `{fallback_version: "12.34"}`. - [ ] `testing/test_basic_api.py::test_empty_pretend_version` - Proposed unit: `test_infer_with_empty_pretend_uses_fallback` - Notes: Set `SETUPTOOLS_SCM_PRETEND_VERSION=""`, infer with fallback. - [ ] `testing/test_basic_api.py::test_empty_pretend_version_named` - Proposed unit: `test_infer_with_empty_named_pretend_uses_fallback` - Notes: Use named pretend env var and fallback. - [ ] `testing/test_regressions.py::test_use_scm_version_callable` - Proposed unit: `test_infer_with_callable_version_scheme` - Notes: Pass callable via `overrides={"version_scheme": callable}` to `infer_version_string`. - [ ] `testing/test_git.py::test_root_relative_to` - Proposed unit: `test_configuration_absolute_root_resolution` - Notes: Assert `Configuration.absolute_root` behavior or use `Configuration.from_data(..., root/relative_to)`; avoid `setup.py`. - [ ] `testing/test_git.py::test_root_search_parent_directories` - Proposed unit: `test_configuration_search_parent_directories` - Notes: Prefer `Configuration(search_parent_directories=True)` + direct `_get_version` or `infer_version_string`. ### Tests to keep as integration/E2E - `testing/test_integration.py::test_integration_function_call_order` - Validates precedence/ordering between `infer_version` and `version_keyword` hooks on `Distribution`. - `testing/test_integration.py::test_distribution_provides_extras` - Verifies installed distribution metadata (extras exposure). - `testing/test_integration.py::test_git_archival_plugin_ignored` - Entry point filtering behavior. - `testing/test_git.py::test_git_version_unnormalized_setuptools` (parameterized) - Asserts difference between file write (`write_to` non-normalized) vs setuptools-normalized dist metadata. Requires setuptools behavior; not reproducible by pure helper. - Maintain a minimal smoke test to ensure `setup.py --version` works end-to-end (one per major path). ### Already covered by unit-level decision tests (no action) - `testing/test_version_inference.py` suite - Exercises `get_version_inference_config` across configuration matrices using `PyProjectData.for_testing`. ### New unit tests to add (pure inference) - [ ] `test_infer_local_scheme_no_local_version` - Use `PyProjectData.for_testing(section_present=True, project_present=True, local_scheme="no-local-version")`. - [ ] `test_infer_with_env_pretend_version_and_metadata` - Set pretend version + metadata env vars; assert combined result via `infer_version_string`. - [ ] `test_infer_respects_nested_scm_git_config` - Provide nested TOML-equivalent via `overrides={"scm": {"git": {"pre_parse": "fail_on_missing_submodules"}}}`. ### Notes and pitfalls - Some behaviors are specific to setuptools (normalization of dist metadata vs written file contents) and should remain integration tests. - Prefer `PyProjectData.for_testing(...)` to avoid file I/O in new unit tests. - For tests that assert version-file writing, call `infer_version_string(..., force_write_version_files=True)` and set `write_to`/`version_file` in overrides. setuptools-scm-9.2.2/testing/__init__.py000066400000000000000000000000001507525030000202760ustar00rootroot00000000000000setuptools-scm-9.2.2/testing/conftest.py000066400000000000000000000065441507525030000204070ustar00rootroot00000000000000from __future__ import annotations import contextlib import os import shutil import sys from pathlib import Path from types import TracebackType from typing import Any from typing import Iterator import pytest from setuptools_scm._run_cmd import run if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self from .wd_wrapper import WorkDir def pytest_configure() -> None: # 2009-02-13T23:31:30+00:00 os.environ["SOURCE_DATE_EPOCH"] = "1234567890" os.environ["SETUPTOOLS_SCM_DEBUG"] = "1" VERSION_PKGS = ["setuptools", "setuptools_scm", "packaging", "build", "wheel"] def pytest_report_header() -> list[str]: from importlib.metadata import version res = [] for pkg in VERSION_PKGS: pkg_version = version(pkg) path = __import__(pkg).__file__ if path and "site-packages" in path: # Replace everything up to and including site-packages with site:: parts = path.split("site-packages", 1) if len(parts) > 1: path = "site:." + parts[1] elif path and str(Path.cwd()) in path: # Replace current working directory with CWD:: path = path.replace(str(Path.cwd()), "CWD:.") res.append(f"{pkg} version {pkg_version} from {path}") return res def pytest_addoption(parser: Any) -> None: group = parser.getgroup("setuptools_scm") group.addoption( "--test-legacy", dest="scm_test_virtualenv", default=False, action="store_true" ) class DebugMode(contextlib.AbstractContextManager): # type: ignore[type-arg] from setuptools_scm import _log as __module def __init__(self) -> None: self.__stack = contextlib.ExitStack() def __enter__(self) -> Self: self.enable() return self def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: self.disable() def enable(self) -> None: self.__stack.enter_context(self.__module.defer_to_pytest()) def disable(self) -> None: self.__stack.close() @pytest.fixture(autouse=True) def debug_mode() -> Iterator[DebugMode]: with DebugMode() as debug_mode: yield debug_mode @pytest.fixture def wd(tmp_path: Path) -> WorkDir: target_wd = tmp_path.resolve() / "wd" target_wd.mkdir() return WorkDir(target_wd) @pytest.fixture(scope="session") def hg_exe() -> str: hg = shutil.which("hg") if hg is None: pytest.skip("hg executable not found") return hg @pytest.fixture def repositories_hg_git(tmp_path: Path) -> tuple[WorkDir, WorkDir]: tmp_path = tmp_path.resolve() path_git = tmp_path / "repo_git" path_git.mkdir() wd = WorkDir(path_git) wd("git init") wd("git config user.email test@example.com") wd('git config user.name "a test"') wd.add_command = "git add ." wd.commit_command = "git commit -m test-{reason}" path_hg = tmp_path / "repo_hg" run(["hg", "clone", path_git, path_hg, "--config", "extensions.hggit="], tmp_path) assert path_hg.exists() with open(path_hg / ".hg/hgrc", "a") as file: file.write("[extensions]\nhggit =\n") wd_hg = WorkDir(path_hg) wd_hg.add_command = "hg add ." wd_hg.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"' return wd_hg, wd setuptools-scm-9.2.2/testing/play_out_381.bash000077500000000000000000000011011507525030000212610ustar00rootroot00000000000000#!/usr/bin/env bash set -euxo pipefail rm -rf y z home venv tmp [ ! -d black ] && git clone https://github.com/psf/black export SETUPTOOLS_SCM_DEBUG=1 export PRE_COMMIT_HOME="$PWD/home" export TMPDIR="$PWD/tmp" git init y git init z git -C z commit --allow-empty -m 'commit!' git -C y submodule add "$PWD/z" cat > "$PWD/y/.git/modules/z/hooks/pre-commit" < None: run([sys.executable, "-c", "print(1)"], cwd=tmp_path) def test_data_from_mime(tmp_path: Path) -> None: tmpfile = tmp_path.joinpath("test.archival") tmpfile.write_bytes(b"name: test\nrevision: 1") res = data_from_mime(str(tmpfile)) assert res == {"name": "test", "revision": "1"} def test_version_from_pkginfo(wd: WorkDir) -> None: wd.write("PKG-INFO", "Version: 0.1") assert wd.get_version() == "0.1" # replicate issue 167 assert wd.get_version(version_scheme="1.{0.distance}.0".format) == "0.1" def assert_root(monkeypatch: pytest.MonkeyPatch, expected_root: str) -> None: """ Patch version_from_scm to simply assert that root is expected root """ def assertion(config: Configuration) -> ScmVersion: assert config.absolute_root == expected_root from packaging.version import Version return ScmVersion(Version("1.0"), config=config) monkeypatch.setattr(setuptools_scm._get_version_impl, "parse_version", assertion) def test_root_parameter_creation(monkeypatch: pytest.MonkeyPatch) -> None: assert_root(monkeypatch, os.getcwd()) setuptools_scm.get_version() def test_root_parameter_pass_by( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: assert_root(monkeypatch, os.fspath(tmp_path)) setuptools_scm.get_version(root=os.fspath(tmp_path)) setuptools_scm.get_version( os.fspath(tmp_path) ) # issue 669 - posarg difference between Configuration and get_version def test_parentdir_prefix(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") p = tmp_path.joinpath("projectname-v12.34") p.mkdir() p.joinpath("setup.py").write_text( """from setuptools import setup setup(use_scm_version={"parentdir_prefix_version": "projectname-"}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "12.34" def test_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") p = tmp_path / "sub/package" p.mkdir(parents=True) p.joinpath("setup.py").write_text( """from setuptools import setup setup(use_scm_version={"fallback_version": "12.34"}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "12.34" def test_empty_pretend_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: # monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") monkeypatch.setenv("SETUPTOOLS_SCM_PRETEND_VERSION", "") p = tmp_path / "sub/package" p.mkdir(parents=True) p.joinpath("setup.py").write_text( """from setuptools import setup setup(use_scm_version={"fallback_version": "12.34"}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "12.34" def test_empty_pretend_version_named( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") monkeypatch.setenv("SETUPTOOLS_SCM_PRETEND_VERSION", "1.23") monkeypatch.setenv("SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MYSCM", "") p = tmp_path.joinpath("sub/package") p.mkdir(parents=True) p.joinpath("setup.py").write_text( """from setuptools import setup setup(name="myscm", use_scm_version={"fallback_version": "12.34"}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "12.34" def test_get_version_blank_tag_regex() -> None: with pytest.warns( DeprecationWarning, match="empty regex for tag regex is invalid, using default" ): setuptools_scm.get_version(tag_regex="") @pytest.mark.parametrize( "version", ["1.0", "1.2.3.dev1+ge871260", "1.2.3.dev15+ge871260.d20180625", "2345"] ) def test_pretended(version: str, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv(setuptools_scm._overrides.PRETEND_KEY, version) assert setuptools_scm.get_version() == version def test_root_relative_to(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: tmp_path.joinpath("setup.cfg").touch() assert_root(monkeypatch, str(tmp_path / "alt")) module = tmp_path / "module/file.py" module.parent.mkdir() module.touch() setuptools_scm.get_version( root="../alt", relative_to=str(module), ) with pytest.warns(UserWarning, match="relative_to is expected to be a file.*"): setuptools_scm.get_version( root="../alt", relative_to=str(module.parent), ) def test_dump_version(tmp_path: Path) -> None: version = "1.0" scm_version = meta(version, config=c) dump_version(tmp_path, version, "first.txt", scm_version=scm_version) def read(name: str) -> str: return tmp_path.joinpath(name).read_text(encoding="utf-8") assert read("first.txt") == "1.0" version = "1.0.dev42" scm_version = meta("1.0", distance=42, config=c) dump_version(tmp_path, version, "first.py", scm_version=scm_version) lines = read("first.py").splitlines() assert lines[-4:] == [ "__version__ = version = '1.0.dev42'", "__version_tuple__ = version_tuple = (1, 0, 'dev42')", "", "__commit_id__ = commit_id = None", ] version = "1.0.1" scm_version = meta("1.0.1", node="g4ac9d2c", config=c) dump_version(tmp_path, version, "second.py", scm_version=scm_version) lines = read("second.py").splitlines() assert lines[-4:] == [ "__version__ = version = '1.0.1'", "__version_tuple__ = version_tuple = (1, 0, 1)", "", "__commit_id__ = commit_id = 'g4ac9d2c'", ] version = "1.0.1+g4ac9d2c" scm_version = meta("1.0.1", node="g4ac9d2c", config=c) dump_version( tmp_path, version, "third.py", scm_version=scm_version, template=template ) lines = read("third.py").splitlines() assert "__version__ = version = '1.0.1+g4ac9d2c'" in lines assert "__version_tuple__ = version_tuple = (1, 0, 1, 'g4ac9d2c')" in lines assert "__sha__ = 'g4ac9d2c'" in lines version = "1.2.3.dev18+gb366d8b.d20210415" scm_version = meta( "1.2.3", node="gb366d8b", distance=18, node_date=date(2021, 4, 15), config=c ) dump_version( tmp_path, version, "fourth.py", scm_version=scm_version, template=template ) lines = read("fourth.py").splitlines() assert "__version__ = version = '1.2.3.dev18+gb366d8b.d20210415'" in lines assert ( "__version_tuple__ = version_tuple = (1, 2, 3, 'dev18', 'gb366d8b.d20210415')" in lines ) assert "__sha__ = 'gb366d8b'" in lines import ast ast.parse(read("fourth.py")) def test_parse_plain_fails(recwarn: pytest.WarningsRecorder) -> None: def parse(root: object) -> str: return "tricked you" with pytest.raises(TypeError): setuptools_scm.get_version(parse=parse) def test_custom_version_cls() -> None: """Test that `normalize` and `version_cls` work as expected""" class MyVersion: def __init__(self, tag_str: str) -> None: self.version = tag_str def __repr__(self) -> str: return f"hello,{self.version}" @property def public(self) -> str: """The public portion of the version (without local part).""" return self.version.split("+")[0] @property def local(self) -> str | None: """The local version segment.""" if "+" in self.version: return self.version.split("+", 1)[1] return None # you can not use normalize=False and version_cls at the same time with pytest.raises( ValueError, match="Providing a custom `version_cls`" " is not permitted when `normalize=False`", ): setuptools_scm.get_version(normalize=False, version_cls=MyVersion) # TODO unfortunately with PRETEND_KEY the preformatted flag becomes True # which bypasses our class. which other mechanism would be ok to use here # to create a test? # monkeypatch.setenv(setuptools_scm.PRETEND_KEY, "1.0.1") # assert setuptools_scm.get_version(version_cls=MyVersion) == "1" def test_internal_get_version_warns_for_version_files(tmp_path: Path) -> None: tmp_path.joinpath("PKG-INFO").write_bytes(b"Version: 0.1") c = Configuration(root=tmp_path, fallback_root=tmp_path) with pytest.warns( DeprecationWarning, match="force_write_version_files ought to be set," " presuming the legacy True value", ): ver = setuptools_scm._get_version(c) assert ver == "0.1" # force write won't write as no version file is configured assert setuptools_scm._get_version(c, force_write_version_files=False) == ver assert setuptools_scm._get_version(c, force_write_version_files=True) == ver setuptools-scm-9.2.2/testing/test_better_root_errors.py000066400000000000000000000147111507525030000235400ustar00rootroot00000000000000""" Tests for better error messages when relative_to is not set. This addresses the issue #279 where error messages should be more helpful when setuptools-scm fails to detect a version but a repository exists in a parent directory. """ from __future__ import annotations import pytest from setuptools_scm import Configuration from setuptools_scm import get_version from setuptools_scm._get_version_impl import _find_scm_in_parents from setuptools_scm._get_version_impl import _version_missing from testing.wd_wrapper import WorkDir def setup_git_repo(wd: WorkDir) -> WorkDir: """Set up a git repository for testing.""" wd("git init") wd("git config user.email test@example.com") wd('git config user.name "a test"') wd.add_command = "git add ." wd.commit_command = "git commit -m test-{reason}" return wd def setup_hg_repo(wd: WorkDir) -> WorkDir: """Set up a mercurial repository for testing.""" try: wd("hg init") wd.add_command = "hg add ." wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"' return wd except Exception: pytest.skip("hg not available") def test_find_scm_in_parents_finds_git(wd: WorkDir) -> None: """Test that _find_scm_in_parents correctly finds git repositories in parent directories.""" # Set up git repo in root setup_git_repo(wd) # Create a subdirectory structure subdir = wd.cwd / "subproject" / "nested" subdir.mkdir(parents=True) # Test from the nested subdirectory config = Configuration(root=str(subdir)) found_scm = _find_scm_in_parents(config) assert found_scm == wd.cwd def test_find_scm_in_parents_finds_hg(wd: WorkDir) -> None: """Test that _find_scm_in_parents correctly finds mercurial repositories in parent directories.""" # Set up hg repo in root setup_hg_repo(wd) # Create a subdirectory structure subdir = wd.cwd / "subproject" / "nested" subdir.mkdir(parents=True) # Test from the nested subdirectory config = Configuration(root=str(subdir)) found_scm = _find_scm_in_parents(config) assert found_scm == wd.cwd def test_find_scm_in_parents_returns_none(wd: WorkDir) -> None: """Test that _find_scm_in_parents returns None when no SCM repository is found.""" # Don't initialize any SCM, just create subdirectories subdir = wd.cwd / "project" / "nested" subdir.mkdir(parents=True) config = Configuration(root=str(subdir)) found_scm = _find_scm_in_parents(config) assert found_scm is None def test_version_missing_with_scm_in_parent(wd: WorkDir) -> None: """Test that _version_missing provides helpful error message when SCM is found in parent.""" # Set up git repo in root setup_git_repo(wd) # Create a subdirectory structure subdir = wd.cwd / "subproject" / "nested" subdir.mkdir(parents=True) # Test error message when relative_to is not set config = Configuration(root=str(subdir), relative_to=None) with pytest.raises(LookupError) as exc_info: _version_missing(config) error_message = str(exc_info.value) # Check that the error message mentions the parent repository assert f"repository was found in a parent directory: {wd.cwd}" in error_message assert "relative_to" in error_message assert "search_parent_directories = true" in error_message assert "setuptools_scm.get_version(relative_to=__file__)" in error_message def test_version_missing_no_scm_found(wd: WorkDir) -> None: """Test that _version_missing provides standard error message when no SCM is found anywhere.""" # Don't initialize any SCM, just create subdirectories subdir = wd.cwd / "project" / "nested" subdir.mkdir(parents=True) config = Configuration(root=str(subdir), relative_to=None) with pytest.raises(LookupError) as exc_info: _version_missing(config) error_message = str(exc_info.value) # Check that it falls back to the standard error message assert ( "Make sure you're either building from a fully intact git repository" in error_message ) assert "repository was found in a parent directory" not in error_message def test_version_missing_with_relative_to_set(wd: WorkDir) -> None: """Test that when relative_to is set, we don't search parents for error messages.""" # Set up git repo in root setup_git_repo(wd) # Create a subdirectory structure subdir = wd.cwd / "subproject" / "nested" subdir.mkdir(parents=True) # Create a dummy file to use as relative_to dummy_file = subdir / "setup.py" dummy_file.write_text("# dummy file", encoding="utf-8") # Test error message when relative_to IS set config = Configuration(root=str(subdir), relative_to=str(dummy_file)) with pytest.raises(LookupError) as exc_info: _version_missing(config) error_message = str(exc_info.value) # Should not mention parent directory when relative_to is set assert "repository was found in a parent directory" not in error_message assert ( "Make sure you're either building from a fully intact git repository" in error_message ) def test_search_parent_directories_works_as_suggested( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that the suggested search_parent_directories=True solution actually works.""" # Set up git repo setup_git_repo(wd) wd.commit_testfile() # Make sure there's a commit for version detection # Create a subdirectory subdir = wd.cwd / "subproject" subdir.mkdir() # Change to the subdirectory monkeypatch.chdir(subdir) # This should work with search_parent_directories=True version = get_version(search_parent_directories=True) assert version is not None assert "0.1.dev" in version def test_integration_better_error_from_nested_directory( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Integration test: get_version from nested directory should give helpful error.""" # Set up git repo setup_git_repo(wd) # Create a subdirectory subdir = wd.cwd / "subproject" subdir.mkdir() # Change to the subdirectory monkeypatch.chdir(subdir) # Try to get version without any configuration with pytest.raises(LookupError) as exc_info: get_version() error_message = str(exc_info.value) # Should suggest helpful solutions assert f"repository was found in a parent directory: {wd.cwd}" in error_message assert "search_parent_directories = true" in error_message setuptools-scm-9.2.2/testing/test_cli.py000066400000000000000000000157161507525030000203710ustar00rootroot00000000000000from __future__ import annotations import io from contextlib import redirect_stdout import pytest from setuptools_scm._cli import main from .conftest import DebugMode from .test_git import wd as wd_fixture # noqa: F401 (evil fixture reuse) from .wd_wrapper import WorkDir PYPROJECT_TOML = "pyproject.toml" PYPROJECT_SIMPLE = "[tool.setuptools_scm]" PYPROJECT_ROOT = '[tool.setuptools_scm]\nroot=".."' def get_output(args: list[str]) -> str: with redirect_stdout(io.StringIO()) as out: main(args) return out.getvalue() warns_cli_root_override = pytest.warns( UserWarning, match="root .. is overridden by the cli arg .*" ) exits_with_not_found = pytest.raises(SystemExit, match="no version found for") def test_cli_find_pyproject( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, debug_mode: DebugMode ) -> None: wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) out = get_output([]) assert out.startswith("0.1.dev1+") with exits_with_not_found: get_output(["--root=.."]) wd.write(PYPROJECT_TOML, PYPROJECT_ROOT) with exits_with_not_found: print(get_output(["-c", PYPROJECT_TOML])) with warns_cli_root_override, exits_with_not_found: get_output(["-c", PYPROJECT_TOML, "--root=.."]) with warns_cli_root_override: out = get_output(["-c", PYPROJECT_TOML, "--root=."]) assert out.startswith("0.1.dev1+") def test_cli_force_version_files( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, debug_mode: DebugMode ) -> None: debug_mode.disable() wd.commit_testfile() wd.write( PYPROJECT_TOML, """ [project] name = "test" [tool.setuptools_scm] version_file = "ver.py" """, ) monkeypatch.chdir(wd.cwd) version_file = wd.cwd.joinpath("ver.py") assert not version_file.exists() get_output([]) assert not version_file.exists() output = get_output(["--force-write-version-files"]) assert version_file.exists() assert output[:5] in version_file.read_text("utf-8") def test_cli_create_archival_file_stable( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test creating stable .git_archival.txt file.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) archival_file = wd.cwd / ".git_archival.txt" assert not archival_file.exists() result = main(["create-archival-file", "--stable"]) assert result == 0 assert archival_file.exists() content = archival_file.read_text("utf-8") expected_lines = [ "node: $Format:%H$", "node-date: $Format:%cI$", "describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$", ] for line in expected_lines: assert line in content # Stable version should not contain ref-names assert "ref-names" not in content def test_cli_create_archival_file_full( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test creating full .git_archival.txt file with branch information.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) archival_file = wd.cwd / ".git_archival.txt" assert not archival_file.exists() result = main(["create-archival-file", "--full"]) assert result == 0 assert archival_file.exists() content = archival_file.read_text("utf-8") expected_lines = [ "node: $Format:%H$", "node-date: $Format:%cI$", "describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$", "ref-names: $Format:%D$", ] for line in expected_lines: assert line in content # Full version should contain warning comment assert "WARNING" in content assert "unstable" in content def test_cli_create_archival_file_exists_no_force( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that existing .git_archival.txt file prevents creation without --force.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) archival_file = wd.cwd / ".git_archival.txt" archival_file.write_text("existing content", encoding="utf-8") # Should fail without --force result = main(["create-archival-file", "--stable"]) assert result == 1 # Content should be unchanged assert archival_file.read_text("utf-8") == "existing content" def test_cli_create_archival_file_exists_with_force( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that --force overwrites existing .git_archival.txt file.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) archival_file = wd.cwd / ".git_archival.txt" archival_file.write_text("existing content", encoding="utf-8") # Should succeed with --force result = main(["create-archival-file", "--stable", "--force"]) assert result == 0 # Content should be updated content = archival_file.read_text("utf-8") assert "existing content" not in content assert "node: $Format:%H$" in content def test_cli_create_archival_file_requires_stable_or_full( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that create-archival-file requires either --stable or --full.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) # Should fail without --stable or --full with pytest.raises(SystemExit): main(["create-archival-file"]) def test_cli_create_archival_file_mutually_exclusive( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that --stable and --full are mutually exclusive.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) # Should fail with both --stable and --full with pytest.raises(SystemExit): main(["create-archival-file", "--stable", "--full"]) def test_cli_create_archival_file_existing_gitattributes( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test behavior when .gitattributes already has export-subst configuration.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) # Create .gitattributes with export-subst configuration gitattributes_file = wd.cwd / ".gitattributes" gitattributes_file.write_text(".git_archival.txt export-subst\n", encoding="utf-8") result = main(["create-archival-file", "--stable"]) assert result == 0 archival_file = wd.cwd / ".git_archival.txt" assert archival_file.exists() def test_cli_create_archival_file_no_gitattributes( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test behavior when .gitattributes doesn't exist or lacks export-subst.""" wd.commit_testfile() wd.write(PYPROJECT_TOML, PYPROJECT_SIMPLE) monkeypatch.chdir(wd.cwd) result = main(["create-archival-file", "--stable"]) assert result == 0 archival_file = wd.cwd / ".git_archival.txt" assert archival_file.exists() setuptools-scm-9.2.2/testing/test_compat.py000066400000000000000000000050201507525030000210700ustar00rootroot00000000000000"""Test compatibility utilities.""" from __future__ import annotations import pytest from setuptools_scm._compat import normalize_path_for_assertion from setuptools_scm._compat import strip_path_suffix def test_normalize_path_for_assertion() -> None: """Test path normalization for assertions.""" # Unix-style paths should remain unchanged assert normalize_path_for_assertion("/path/to/file") == "/path/to/file" # Windows-style paths should be normalized assert normalize_path_for_assertion(r"C:\path\to\file") == "C:/path/to/file" assert normalize_path_for_assertion(r"path\to\file") == "path/to/file" # Mixed paths should be normalized assert normalize_path_for_assertion(r"C:\path/to\file") == "C:/path/to/file" # Already normalized paths should remain unchanged assert normalize_path_for_assertion("path/to/file") == "path/to/file" def test_strip_path_suffix_success() -> None: """Test successful path suffix stripping.""" # Unix-style paths assert strip_path_suffix("/home/user/project", "project") == "/home/user/" assert ( strip_path_suffix("/home/user/project/subdir", "project/subdir") == "/home/user/" ) # Windows-style paths assert ( strip_path_suffix("C:\\Users\\user\\project", "project") == "C:\\Users\\user\\" ) assert ( strip_path_suffix("C:\\Users\\user\\project\\subdir", "project/subdir") == "C:\\Users\\user\\" ) # Mixed paths should work due to normalization assert ( strip_path_suffix("C:\\Users\\user\\project", "project") == "C:\\Users\\user\\" ) assert strip_path_suffix("/home/user/project", "project") == "/home/user/" # Edge cases assert strip_path_suffix("project", "project") == "" assert strip_path_suffix("/project", "project") == "/" def test_strip_path_suffix_failure() -> None: """Test failed path suffix stripping.""" with pytest.raises(AssertionError, match="Path assertion failed"): strip_path_suffix("/home/user/project", "other") with pytest.raises(AssertionError, match="Custom error"): strip_path_suffix("/home/user/project", "other", "Custom error") def test_integration_example() -> None: """Test the integration pattern used in the codebase.""" # Simulate the pattern used in git.py and _file_finders/git.py full_path = r"C:\\Users\\user\\project\\subdir" suffix = "subdir" # Now this is a single operation prefix = strip_path_suffix(full_path, suffix) assert prefix == r"C:\\Users\\user\\project\\" setuptools-scm-9.2.2/testing/test_config.py000066400000000000000000000061361507525030000210630ustar00rootroot00000000000000from __future__ import annotations import re import textwrap from pathlib import Path import pytest from setuptools_scm import Configuration @pytest.mark.parametrize( ("tag", "expected_version"), [ ("apache-arrow-0.9.0", "0.9.0"), ("arrow-0.9.0", "0.9.0"), ("arrow-0.9.0-rc", "0.9.0-rc"), ("arrow-1", "1"), ("arrow-1+", "1"), ("arrow-1+foo", "1"), ("arrow-1.1+foo", "1.1"), ("v1.1", "v1.1"), ("V1.1", "V1.1"), ], ) def test_tag_regex(tag: str, expected_version: str) -> None: config = Configuration() match = config.tag_regex.match(tag) assert match version = match.group("version") assert version == expected_version def test_config_from_pyproject(tmp_path: Path) -> None: fn = tmp_path / "pyproject.toml" fn.write_text( textwrap.dedent( """ [tool.setuptools_scm] [project] description = "Factory ⸻ A code generator 🏭" authors = [{name = "Łukasz Langa"}] """ ), encoding="utf-8", ) assert Configuration.from_file(str(fn)) def test_config_regex_init() -> None: tag_regex = re.compile(r"v(\d+)") conf = Configuration(tag_regex=tag_regex) assert conf.tag_regex is tag_regex def test_config_from_file_protects_relative_to(tmp_path: Path) -> None: fn = tmp_path / "pyproject.toml" fn.write_text( textwrap.dedent( """ [tool.setuptools_scm] relative_to = "dont_use_me" [project] description = "Factory ⸻ A code generator 🏭" authors = [{name = "Łukasz Langa"}] """ ), encoding="utf-8", ) with pytest.warns( UserWarning, match=".*pyproject.toml: at \\[tool.setuptools_scm\\]\n" "ignoring value relative_to='dont_use_me'" " as its always relative to the config file", ): assert Configuration.from_file(str(fn)) def test_config_overrides(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: fn = tmp_path / "pyproject.toml" fn.write_text( textwrap.dedent( """ [tool.setuptools_scm] root = "." [project] name = "teSt-.a" """ ), encoding="utf-8", ) pristine = Configuration.from_file(fn) monkeypatch.setenv( "SETUPTOOLS_SCM_OVERRIDES_FOR_TEST_A", '{root="..", fallback_root=".."}' ) overridden = Configuration.from_file(fn) assert pristine.root != overridden.root assert pristine.fallback_root != overridden.fallback_root @pytest.mark.parametrize( "tag_regex", [ r".*", r"(.+)(.+)", r"((.*))", ], ) def test_config_bad_regex(tag_regex: str) -> None: with pytest.raises( ValueError, match=( f"Expected tag_regex '{re.escape(tag_regex)}' to contain a single match" " group or a group named 'version' to identify the version part of any" " tag." ), ): Configuration(tag_regex=re.compile(tag_regex)) setuptools-scm-9.2.2/testing/test_deprecation.py000066400000000000000000000017231507525030000221100ustar00rootroot00000000000000"""Test deprecation warnings and their exact text.""" from pathlib import Path import pytest from setuptools_scm._integration.deprecation import warn_dynamic_version def test_warn_dynamic_version_full_text() -> None: """Test the complete warning text for warn_dynamic_version function.""" test_path = Path("test_file.toml") expected_warning = ( f"{test_path}: at [test.section]\n" "test_expression is forcing setuptools to override the version setuptools-scm did already set\n" "When using setuptools-scm it's invalid to use setuptools dynamic version as well, please remove it.\n" "Setuptools-scm is responsible for setting the version, forcing setuptools to override creates errors." ) with pytest.warns(UserWarning) as warning_info: # noqa: PT030 warn_dynamic_version(test_path, "test.section", "test_expression") assert len(warning_info) == 1 assert str(warning_info[0].message) == expected_warning setuptools-scm-9.2.2/testing/test_file_finder.py000066400000000000000000000222571507525030000220660ustar00rootroot00000000000000from __future__ import annotations import os import sys from typing import Iterable import pytest from setuptools_scm._file_finders import find_files from .wd_wrapper import WorkDir @pytest.fixture(params=["git", "hg"]) def inwd( request: pytest.FixtureRequest, wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> WorkDir: param: str = request.param # type: ignore[attr-defined] if param == "git": try: wd("git init") except OSError: pytest.skip("git executable not found") wd("git config user.email test@example.com") wd('git config user.name "a test"') wd.add_command = "git add ." wd.commit_command = "git commit -m test-{reason}" elif param == "hg": try: wd("hg init") except OSError: pytest.skip("hg executable not found") wd.add_command = "hg add ." wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"' (wd.cwd / "file1").touch() adir = wd.cwd / "adir" adir.mkdir() (adir / "filea").touch() bdir = wd.cwd / "bdir" bdir.mkdir() (bdir / "fileb").touch() if request.node.get_closest_marker("skip_commit") is None: wd.add_and_commit() monkeypatch.chdir(wd.cwd) return wd def _sep(paths: Iterable[str]) -> set[str]: return {path.replace("/", os.path.sep) for path in paths} def test_basic(inwd: WorkDir) -> None: assert set(find_files()) == _sep({"file1", "adir/filea", "bdir/fileb"}) assert set(find_files(".")) == _sep({"./file1", "./adir/filea", "./bdir/fileb"}) assert set(find_files("adir")) == _sep({"adir/filea"}) def test_whitespace(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "space file").touch() inwd.add_and_commit() assert set(find_files("adir")) == _sep({"adir/space file", "adir/filea"}) def test_case(inwd: WorkDir) -> None: (inwd.cwd / "CamelFile").touch() (inwd.cwd / "file2").touch() inwd.add_and_commit() assert set(find_files()) == _sep( {"CamelFile", "file2", "file1", "adir/filea", "bdir/fileb"} ) @pytest.mark.skipif( os.path.normcase("B") != os.path.normcase("b"), reason="case sensitive filesystem" ) def test_case_cwd_evil(inwd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: (inwd.cwd / "CamelFile").touch() (inwd.cwd / "file2").touch() inwd.add_and_commit() monkeypatch.chdir(inwd.cwd.parent.joinpath(inwd.cwd.name.capitalize())) assert set(find_files()) == _sep( {"CamelFile", "file2", "file1", "adir/filea", "bdir/fileb"} ) @pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported") def test_symlink_dir(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir") inwd.add_and_commit() assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"}) @pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported") def test_symlink_dir_source_not_in_scm(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir") assert set(find_files("adir")) == _sep({"adir/filea"}) @pytest.mark.skipif( sys.platform == "win32", reason="symlinks to files not supported on windows" ) def test_symlink_file(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "file1link").symlink_to("../file1") inwd.add_and_commit() assert set(find_files("adir")) == _sep( {"adir/filea", "adir/file1link"} ) # -> ../file1 @pytest.mark.skipif( sys.platform == "win32", reason="symlinks to files not supported on windows" ) def test_symlink_file_source_not_in_scm(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "file1link").symlink_to("../file1") assert set(find_files("adir")) == _sep({"adir/filea"}) @pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported") def test_symlink_loop(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "loop").symlink_to("../adir") inwd.add_and_commit() assert set(find_files("adir")) == _sep({"adir/filea", "adir/loop"}) # -> ../adir @pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported") def test_symlink_loop_outside_path(inwd: WorkDir) -> None: (inwd.cwd / "bdir" / "loop").symlink_to("../bdir") (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir") inwd.add_and_commit() assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"}) @pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported") def test_symlink_dir_out_of_git(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "outsidedirlink").symlink_to(os.path.join(__file__, "..")) inwd.add_and_commit() assert set(find_files("adir")) == _sep({"adir/filea"}) @pytest.mark.skipif( sys.platform == "win32", reason="symlinks to files not supported on windows" ) def test_symlink_file_out_of_git(inwd: WorkDir) -> None: (inwd.cwd / "adir" / "outsidefilelink").symlink_to(__file__) inwd.add_and_commit() assert set(find_files("adir")) == _sep({"adir/filea"}) @pytest.mark.parametrize("path_add", ["{cwd}", "{cwd}" + os.pathsep + "broken"]) def test_ignore_root( inwd: WorkDir, monkeypatch: pytest.MonkeyPatch, path_add: str ) -> None: monkeypatch.setenv("SETUPTOOLS_SCM_IGNORE_VCS_ROOTS", path_add.format(cwd=inwd.cwd)) assert find_files() == [] def test_empty_root(inwd: WorkDir) -> None: subdir = inwd.cwd / "cdir" / "subdir" subdir.mkdir(parents=True) (subdir / "filec").touch() inwd.add_and_commit() assert set(find_files("cdir")) == _sep({"cdir/subdir/filec"}) def test_empty_subdir(inwd: WorkDir) -> None: subdir = inwd.cwd / "adir" / "emptysubdir" / "subdir" subdir.mkdir(parents=True) (subdir / "xfile").touch() inwd.add_and_commit() assert set(find_files("adir")) == _sep( {"adir/filea", "adir/emptysubdir/subdir/xfile"} ) @pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows") def test_double_include_through_symlink(inwd: WorkDir) -> None: (inwd.cwd / "data").mkdir() (inwd.cwd / "data" / "datafile").touch() (inwd.cwd / "adir" / "datalink").symlink_to("../data") (inwd.cwd / "adir" / "filealink").symlink_to("filea") inwd.add_and_commit() assert set(find_files()) == _sep( { "file1", "adir/datalink", # -> ../data "adir/filealink", # -> filea "adir/filea", "bdir/fileb", "data/datafile", } ) @pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows") def test_symlink_not_in_scm_while_target_is(inwd: WorkDir) -> None: (inwd.cwd / "data").mkdir() (inwd.cwd / "data" / "datafile").touch() inwd.add_and_commit() (inwd.cwd / "adir" / "datalink").symlink_to("../data") (inwd.cwd / "adir" / "filealink").symlink_to("filea") assert set(find_files()) == _sep( { "file1", "adir/filea", # adir/datalink and adir/afilelink not included # because the symlink_to themselves are not in scm "bdir/fileb", "data/datafile", } ) @pytest.mark.issue(587) @pytest.mark.skip_commit def test_not_commited(inwd: WorkDir) -> None: assert find_files() == [] def test_unexpanded_git_archival(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: # When substitutions in `.git_archival.txt` are not expanded, files should # not be automatically listed. monkeypatch.chdir(wd.cwd) (wd.cwd / ".git_archival.txt").write_text("node: $Format:%H$", encoding="utf-8") (wd.cwd / "file1.txt").touch() assert find_files() == [] @pytest.mark.parametrize("archive_file", [".git_archival.txt", ".hg_archival.txt"]) def test_archive( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, archive_file: str ) -> None: # When substitutions in `.git_archival.txt` are not expanded, files should # not be automatically listed. monkeypatch.chdir(wd.cwd) sha = "a1bda3d984d1a40d7b00ae1d0869354d6d503001" (wd.cwd / archive_file).write_text(f"node: {sha}", encoding="utf-8") (wd.cwd / "data").mkdir() (wd.cwd / "data" / "datafile").touch() datalink = wd.cwd / "data" / "datalink" if sys.platform != "win32": datalink.symlink_to("data/datafile") else: os.link("data/datafile", datalink) assert set(find_files()) == _sep({archive_file, "data/datafile", "data/datalink"}) @pytest.fixture def hg_wd(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> WorkDir: try: wd("hg init") except OSError: pytest.skip("hg executable not found") (wd.cwd / "file").touch() wd("hg add file") monkeypatch.chdir(wd.cwd) return wd def test_hg_gone(hg_wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PATH", str(hg_wd.cwd / "not-existing")) assert set(find_files()) == set() def test_hg_command_from_env( hg_wd: WorkDir, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest, hg_exe: str, ) -> None: with monkeypatch.context() as m: m.setenv("SETUPTOOLS_SCM_HG_COMMAND", hg_exe) m.setenv("PATH", str(hg_wd.cwd / "not-existing")) # No module reloading needed - runtime configuration works immediately assert set(find_files()) == {"file"} setuptools-scm-9.2.2/testing/test_functions.py000066400000000000000000000302571507525030000216270ustar00rootroot00000000000000from __future__ import annotations import shutil import subprocess from pathlib import Path import pytest from setuptools_scm import Configuration from setuptools_scm import dump_version from setuptools_scm import get_version from setuptools_scm._overrides import PRETEND_KEY from setuptools_scm._run_cmd import has_command from setuptools_scm.version import format_version from setuptools_scm.version import guess_next_version from setuptools_scm.version import meta from setuptools_scm.version import tag_to_version c = Configuration() @pytest.mark.parametrize( ("tag", "expected"), [ ("1.1", "1.2"), ("1.2.dev", "1.2"), ("1.1a2", "1.1a3"), pytest.param( "23.24.post2+deadbeef", "23.24.post3", marks=pytest.mark.filterwarnings( "ignore:.*will be stripped of its suffix.*:UserWarning" ), ), ], ) def test_next_tag(tag: str, expected: str) -> None: version = meta(tag, config=c) assert guess_next_version(version) == expected VERSIONS = { "exact": meta("1.1", distance=0, dirty=False, config=c), "dirty": meta("1.1", distance=0, dirty=True, config=c), "distance-clean": meta("1.1", distance=3, dirty=False, config=c), "distance-dirty": meta("1.1", distance=3, dirty=True, config=c), } # Versions with build metadata in the tag VERSIONS_WITH_BUILD_METADATA = { "exact-build": meta("1.1+build.123", distance=0, dirty=False, config=c), "dirty-build": meta("1.1+build.123", distance=0, dirty=True, config=c), "distance-clean-build": meta("1.1+build.123", distance=3, dirty=False, config=c), "distance-dirty-build": meta("1.1+build.123", distance=3, dirty=True, config=c), "exact-ci": meta("2.0.0+ci.456", distance=0, dirty=False, config=c), "dirty-ci": meta("2.0.0+ci.456", distance=0, dirty=True, config=c), "distance-clean-ci": meta("2.0.0+ci.456", distance=2, dirty=False, config=c), "distance-dirty-ci": meta("2.0.0+ci.456", distance=2, dirty=True, config=c), } @pytest.mark.parametrize( ("version", "version_scheme", "local_scheme", "expected"), [ ("exact", "guess-next-dev", "node-and-date", "1.1"), ("dirty", "guess-next-dev", "node-and-date", "1.2.dev0+d20090213"), ("dirty", "guess-next-dev", "no-local-version", "1.2.dev0"), ("distance-clean", "guess-next-dev", "node-and-date", "1.2.dev3"), ("distance-dirty", "guess-next-dev", "node-and-date", "1.2.dev3+d20090213"), ("exact", "post-release", "node-and-date", "1.1"), ("dirty", "post-release", "node-and-date", "1.1.post0+d20090213"), ("distance-clean", "post-release", "node-and-date", "1.1.post3"), ("distance-dirty", "post-release", "node-and-date", "1.1.post3+d20090213"), ], ) def test_format_version( version: str, version_scheme: str, local_scheme: str, expected: str ) -> None: from dataclasses import replace scm_version = VERSIONS[version] configured_version = replace( scm_version, config=replace( scm_version.config, version_scheme=version_scheme, local_scheme=local_scheme ), ) assert format_version(configured_version) == expected @pytest.mark.parametrize( ("version", "version_scheme", "local_scheme", "expected"), [ # Exact matches should preserve build metadata from tag ("exact-build", "guess-next-dev", "node-and-date", "1.1+build.123"), ("exact-build", "guess-next-dev", "no-local-version", "1.1+build.123"), ("exact-ci", "guess-next-dev", "node-and-date", "2.0.0+ci.456"), ("exact-ci", "guess-next-dev", "no-local-version", "2.0.0+ci.456"), # Dirty exact matches - version scheme treats dirty as non-exact, build metadata preserved ( "dirty-build", "guess-next-dev", "node-and-date", "1.2.dev0+build.123.d20090213", ), ("dirty-build", "guess-next-dev", "no-local-version", "1.2.dev0+build.123"), ("dirty-ci", "guess-next-dev", "node-and-date", "2.0.1.dev0+ci.456.d20090213"), # Distance cases - build metadata should be preserved and combined with SCM data ( "distance-clean-build", "guess-next-dev", "node-and-date", "1.2.dev3+build.123", ), ( "distance-clean-build", "guess-next-dev", "no-local-version", "1.2.dev3+build.123", ), ("distance-clean-ci", "guess-next-dev", "node-and-date", "2.0.1.dev2+ci.456"), # Distance + dirty cases - build metadata should be preserved and combined with SCM data ( "distance-dirty-build", "guess-next-dev", "node-and-date", "1.2.dev3+build.123.d20090213", ), ( "distance-dirty-ci", "guess-next-dev", "node-and-date", "2.0.1.dev2+ci.456.d20090213", ), # Post-release scheme tests ("exact-build", "post-release", "node-and-date", "1.1+build.123"), ( "dirty-build", "post-release", "node-and-date", "1.1.post0+build.123.d20090213", ), ( "distance-clean-build", "post-release", "node-and-date", "1.1.post3+build.123", ), ( "distance-dirty-build", "post-release", "node-and-date", "1.1.post3+build.123.d20090213", ), ], ) def test_format_version_with_build_metadata( version: str, version_scheme: str, local_scheme: str, expected: str ) -> None: """Test format_version with tags that contain build metadata.""" from dataclasses import replace from packaging.version import Version scm_version = VERSIONS_WITH_BUILD_METADATA[version] configured_version = replace( scm_version, config=replace( scm_version.config, version_scheme=version_scheme, local_scheme=local_scheme ), ) result = format_version(configured_version) # Validate result is a valid PEP 440 version parsed = Version(result) assert str(parsed) == result, f"Result should be valid PEP 440: {result}" assert result == expected, f"Expected {expected}, got {result}" def test_dump_version_doesnt_bail_on_value_error(tmp_path: Path) -> None: write_to = "VERSION" version = str(VERSIONS["exact"].tag) scm_version = meta(VERSIONS["exact"].tag, config=c) with pytest.raises(ValueError, match=r"^bad file format:"): dump_version(tmp_path, version, write_to, scm_version=scm_version) @pytest.mark.parametrize( "version", ["1.0", "1.2.3.dev1+ge871260", "1.2.3.dev15+ge871260.d20180625"] ) def test_dump_version_works_with_pretend( version: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setenv(PRETEND_KEY, version) name = "VERSION.txt" target = tmp_path.joinpath(name) get_version(root=tmp_path, write_to=name) assert target.read_text(encoding="utf-8") == version def test_dump_version_modern(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: version = "1.2.3" monkeypatch.setenv(PRETEND_KEY, version) name = "VERSION.txt" project = tmp_path.joinpath("project") target = project.joinpath(name) project.mkdir() get_version(root="..", relative_to=target, version_file=name) assert target.read_text(encoding="utf-8") == version def dump_a_version(tmp_path: Path) -> None: from setuptools_scm._integration.dump_version import write_version_to_path version = "1.2.3" scm_version = meta(version, config=c) write_version_to_path( tmp_path / "VERSION.py", template=None, version=version, scm_version=scm_version ) def test_dump_version_on_old_python(tmp_path: Path) -> None: python37 = shutil.which("python3.7") if python37 is None: pytest.skip("python3.7 not found") dump_a_version(tmp_path) subprocess.run( [python37, "-c", "import VERSION;print(VERSION.version)"], cwd=tmp_path, check=True, ) def test_dump_version_mypy(tmp_path: Path) -> None: mypy = shutil.which("mypy") if mypy is None: pytest.skip("mypy not found") dump_a_version(tmp_path) subprocess.run( [mypy, "--python-version=3.8", "--strict", "VERSION.py"], cwd=tmp_path, check=True, ) def test_dump_version_flake8(tmp_path: Path) -> None: flake8 = shutil.which("flake8") if flake8 is None: pytest.skip("flake8 not found") dump_a_version(tmp_path) subprocess.run([flake8, "VERSION.py"], cwd=tmp_path, check=True) def test_dump_version_ruff(tmp_path: Path) -> None: ruff = shutil.which("ruff") if ruff is None: pytest.skip("ruff not found") dump_a_version(tmp_path) subprocess.run([ruff, "check", "--no-fix", "VERSION.py"], cwd=tmp_path, check=True) def test_has_command() -> None: with pytest.warns(RuntimeWarning, match="yadayada"): assert not has_command("yadayada_setuptools_aint_ne") def test_has_command_logs_stderr(caplog: pytest.LogCaptureFixture) -> None: """ If the name provided to has_command() exists as a command, but gives a non-zero return code, there should be a log message generated. """ with pytest.warns(RuntimeWarning, match="ls"): has_command("ls", ["--a-flag-that-doesnt-exist-should-give-output-on-stderr"]) found_it = False for record in caplog.records: if "returned non-zero. This is stderr" in record.message: found_it = True assert found_it, "Did not find expected log record for " @pytest.mark.parametrize( ("tag", "expected_version"), [ ("1.1", "1.1"), ("release-1.1", "1.1"), pytest.param("3.3.1-rc26", "3.3.1rc26", marks=pytest.mark.issue(266)), ], ) def test_tag_to_version(tag: str, expected_version: str) -> None: version = str(tag_to_version(tag, c)) assert version == expected_version def test_write_version_to_path_deprecation_warning_none(tmp_path: Path) -> None: """Test that write_version_to_path warns when scm_version=None is passed.""" from setuptools_scm._integration.dump_version import write_version_to_path target_file = tmp_path / "version.py" # This should raise a deprecation warning when scm_version=None is explicitly passed with pytest.warns( DeprecationWarning, match="write_version_to_path called without scm_version" ): write_version_to_path( target=target_file, template=None, # Use default template version="1.2.3", scm_version=None, # Explicitly passing None should warn ) # Verify the file was created and contains the expected content assert target_file.exists() content = target_file.read_text(encoding="utf-8") # Check that the version is correctly formatted assert "__version__ = version = '1.2.3'" in content assert "__version_tuple__ = version_tuple = (1, 2, 3)" in content # Check that commit_id is set to None when scm_version is None assert "__commit_id__ = commit_id = None" in content def test_write_version_to_path_deprecation_warning_missing(tmp_path: Path) -> None: """Test that write_version_to_path warns when scm_version parameter is not provided.""" from setuptools_scm._integration.dump_version import write_version_to_path target_file = tmp_path / "version.py" # This should raise a deprecation warning when scm_version is not provided with pytest.warns( DeprecationWarning, match="write_version_to_path called without scm_version" ): write_version_to_path( target=target_file, template=None, # Use default template version="1.2.3", # scm_version not provided - should warn ) # Verify the file was created and contains the expected content assert target_file.exists() content = target_file.read_text(encoding="utf-8") # Check that the version is correctly formatted assert "__version__ = version = '1.2.3'" in content assert "__version_tuple__ = version_tuple = (1, 2, 3)" in content # Check that commit_id is set to None when scm_version is None assert "__commit_id__ = commit_id = None" in content setuptools-scm-9.2.2/testing/test_git.py000066400000000000000000000714361507525030000204060ustar00rootroot00000000000000from __future__ import annotations import contextlib import os import shutil import subprocess import sys from datetime import date from datetime import datetime from datetime import timezone from os.path import join as opj from pathlib import Path from textwrap import dedent from typing import Generator from unittest.mock import Mock from unittest.mock import patch import pytest import setuptools_scm._file_finders from setuptools_scm import Configuration from setuptools_scm import NonNormalizedVersion from setuptools_scm import git from setuptools_scm._file_finders.git import git_find_files from setuptools_scm._run_cmd import CommandNotFoundError from setuptools_scm._run_cmd import CompletedProcess from setuptools_scm._run_cmd import has_command from setuptools_scm._run_cmd import run from setuptools_scm.git import archival_to_version from setuptools_scm.version import format_version from .conftest import DebugMode from .wd_wrapper import WorkDir pytestmark = pytest.mark.skipif( not has_command("git", warn=False), reason="git executable not found" ) def setup_git_wd(wd: WorkDir, monkeypatch: pytest.MonkeyPatch | None = None) -> WorkDir: """Set up a WorkDir with git initialized and configured for testing.""" if monkeypatch: monkeypatch.delenv("HOME", raising=False) wd("git init") wd("git config user.email test@example.com") wd('git config user.name "a test"') wd.add_command = "git add ." wd.commit_command = "git commit -m test-{reason}" return wd @pytest.fixture(name="wd") def wd(wd: WorkDir, monkeypatch: pytest.MonkeyPatch, debug_mode: DebugMode) -> WorkDir: debug_mode.disable() setup_git_wd(wd, monkeypatch) debug_mode.enable() return wd @pytest.mark.parametrize( ("given", "tag", "number", "node", "dirty"), [ ("3.3.1-rc26-0-g9df187b", "3.3.1-rc26", 0, "g9df187b", False), ("17.33.0-rc-17-g38c3047c0", "17.33.0-rc", 17, "g38c3047c0", False), ], ) def test_parse_describe_output( given: str, tag: str, number: int, node: str, dirty: bool ) -> None: parsed = git._git_parse_describe(given) assert parsed == (tag, number, node, dirty) def test_root_relative_to(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") p = wd.cwd.joinpath("sub/package") p.mkdir(parents=True) p.joinpath("setup.py").write_text( """from setuptools import setup setup(use_scm_version={"root": "../..", "relative_to": __file__}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "0.1.dev0+d20090213" def test_root_search_parent_directories( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") p = wd.cwd.joinpath("sub/package") p.mkdir(parents=True) p.joinpath("setup.py").write_text( """from setuptools import setup setup(use_scm_version={"search_parent_directories": True}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "0.1.dev0+d20090213" def test_git_gone(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PATH", str(wd.cwd / "not-existing")) wd.write("pyproject.toml", "[tool.setuptools_scm]") with pytest.raises(CommandNotFoundError, match=r"git"): git.parse(wd.cwd, Configuration(), git.DEFAULT_DESCRIBE) assert wd.get_version(fallback_version="1.0") == "1.0" @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/298") @pytest.mark.issue(403) def test_file_finder_no_history(wd: WorkDir, caplog: pytest.LogCaptureFixture) -> None: file_list = git_find_files(str(wd.cwd)) assert file_list == [] assert "listing git files failed - pretending there aren't any" in caplog.text @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/281") def test_parse_call_order(wd: WorkDir) -> None: git.parse(str(wd.cwd), Configuration(), git.DEFAULT_DESCRIBE) def sudo_devnull( args: list[str | os.PathLike[str]], check: bool = False ) -> subprocess.CompletedProcess[bytes]: """shortcut to run sudo with non-interactive input""" return subprocess.run( ["sudo", *args], stdin=subprocess.DEVNULL, check=check, ) @contextlib.contextmanager def break_folder_permissions(path: Path) -> Generator[None, None, None]: """break the permissions of a folder for a while""" if not shutil.which("sudo"): pytest.skip("sudo executable not found") original_stat = path.stat() proc = sudo_devnull(["chown", "-R", "12345", path]) if proc.returncode != 0: pytest.xfail("Failed to change ownership, is passwordless sudo available?") try: sudo_devnull(["chmod", "a+r", path], check=True) sudo_devnull(["chgrp", "-R", "12345", path], check=True) yield finally: # Restore the ownership sudo_devnull(["chown", "-R", str(original_stat.st_uid), path], check=True) sudo_devnull(["chgrp", "-R", str(original_stat.st_gid), path], check=True) @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/707") def test_not_owner(wd: WorkDir) -> None: with break_folder_permissions(wd.cwd): assert git.parse(str(wd.cwd), Configuration()) def test_version_from_git(wd: WorkDir) -> None: assert wd.get_version() == "0.1.dev0+d20090213" parsed = git.parse(str(wd.cwd), Configuration(), git.DEFAULT_DESCRIBE) assert parsed is not None assert parsed.branch in ("master", "main") wd.commit_testfile() assert wd.get_version().startswith("0.1.dev1+g") assert not wd.get_version().endswith("1-") wd("git tag v0.1") assert wd.get_version() == "0.1" wd.write("test.txt", "test2") assert wd.get_version().startswith("0.2.dev0+g") wd.commit_testfile() assert wd.get_version().startswith("0.2.dev1+g") wd("git tag version-0.2") assert wd.get_version().startswith("0.2") wd.commit_testfile() wd("git tag version-0.2.post210+gbe48adfpost3+g0cc25f2") with pytest.warns( UserWarning, match="tag '.*' will be stripped of its suffix '.*'" ): assert wd.get_version().startswith("0.2") wd.commit_testfile() wd("git tag 17.33.0-rc") assert wd.get_version() == "17.33.0rc0" # custom normalization assert wd.get_version(normalize=False) == "17.33.0-rc" assert wd.get_version(version_cls=NonNormalizedVersion) == "17.33.0-rc" assert ( wd.get_version(version_cls="setuptools_scm.NonNormalizedVersion") == "17.33.0-rc" ) setup_py_with_normalize: dict[str, str] = { "false": """ from setuptools import setup setup(use_scm_version={'normalize': False, 'write_to': 'VERSION.txt'}) """, "with_created_class": """ from setuptools import setup class MyVersion: def __init__(self, tag_str: str): self.version = tag_str def __repr__(self): return self.version @property def public(self): return self.version.split('+')[0] @property def local(self): if '+' in self.version: return self.version.split('+', 1)[1] return None setup(use_scm_version={'version_cls': MyVersion, 'write_to': 'VERSION.txt'}) """, "with_named_import": """ from setuptools import setup setup(use_scm_version={ 'version_cls': 'setuptools_scm.NonNormalizedVersion', 'write_to': 'VERSION.txt' }) """, } @pytest.mark.parametrize( "setup_py_txt", [pytest.param(text, id=key) for key, text in setup_py_with_normalize.items()], ) def test_git_version_unnormalized_setuptools( setup_py_txt: str, wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """ Test that when integrating with setuptools without normalization, the version is not normalized in write_to files, but still normalized by setuptools for the final dist metadata. """ # monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") monkeypatch.chdir(wd.cwd) wd.write("setup.py", dedent(setup_py_txt)) # do git operations and tag wd.commit_testfile() wd("git tag 17.33.0-rc1") # setuptools still normalizes using packaging.Version (removing the dash) res = wd([sys.executable, "setup.py", "--version"]) assert res == "17.33.0rc1" # but the version tag in the file is non-normalized (with the dash) assert wd.cwd.joinpath("VERSION.txt").read_text(encoding="utf-8") == "17.33.0-rc1" @pytest.mark.issue(179) def test_unicode_version_scheme(wd: WorkDir) -> None: scheme = b"guess-next-dev".decode("ascii") assert wd.get_version(version_scheme=scheme) @pytest.mark.issue(108) @pytest.mark.issue(109) def test_git_worktree(wd: WorkDir) -> None: wd.write("test.txt", "test2") # untracked files dont change the state assert wd.get_version() == "0.1.dev0+d20090213" wd("git add test.txt") assert wd.get_version().startswith("0.1.dev0+d") @pytest.mark.issue(86) @pytest.mark.parametrize("today", [False, True]) def test_git_dirty_notag( today: bool, wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: if today: monkeypatch.delenv("SOURCE_DATE_EPOCH", raising=False) wd.commit_testfile() wd.write("test.txt", "test2") wd("git add test.txt") version = wd.get_version() if today: # the date on the tag is in UTC tag = datetime.now(timezone.utc).date().strftime(".d%Y%m%d") else: tag = ".d20090213" assert version.startswith("0.1.dev1+g") assert version.endswith(tag) @pytest.mark.issue(193) @pytest.mark.xfail(reason="sometimes relative path results") def test_git_worktree_support(wd: WorkDir, tmp_path: Path) -> None: wd.commit_testfile() worktree = tmp_path / "work_tree" wd(f"git worktree add -b work-tree {worktree}") res = run([sys.executable, "-m", "setuptools_scm", "ls"], cwd=worktree) assert "test.txt" in res.stdout assert str(worktree) in res.stdout @pytest.fixture def shallow_wd(wd: WorkDir, tmp_path: Path) -> Path: wd.commit_testfile() wd.commit_testfile() wd.commit_testfile() target = tmp_path / "wd_shallow" run(["git", "clone", f"file://{wd.cwd}", target, "--depth=1"], tmp_path, check=True) return target def test_git_parse_shallow_warns( shallow_wd: Path, recwarn: pytest.WarningsRecorder ) -> None: git.parse(shallow_wd, Configuration()) print(list(recwarn)) msg = recwarn.pop() assert "is shallow and may cause errors" in str(msg.message) def test_git_parse_shallow_fail(shallow_wd: Path) -> None: with pytest.raises(ValueError, match="git fetch"): git.parse(str(shallow_wd), Configuration(), pre_parse=git.fail_on_shallow) def test_git_shallow_autocorrect( shallow_wd: Path, recwarn: pytest.WarningsRecorder ) -> None: git.parse(str(shallow_wd), Configuration(), pre_parse=git.fetch_on_shallow) msg = recwarn.pop() assert "git fetch was used to rectify" in str(msg.message) git.parse(str(shallow_wd), Configuration(), pre_parse=git.fail_on_shallow) def test_find_files_stop_at_root_git(wd: WorkDir) -> None: wd.commit_testfile() project = wd.cwd / "project" project.mkdir() project.joinpath("setup.cfg").touch() assert setuptools_scm._file_finders.find_files(str(project)) == [] @pytest.mark.issue(128) def test_parse_no_worktree(tmp_path: Path) -> None: ret = git.parse(str(tmp_path), Configuration(root=str(tmp_path))) assert ret is None def test_alphanumeric_tags_match(wd: WorkDir) -> None: wd.commit_testfile() wd("git tag newstyle-development-started") assert wd.get_version().startswith("0.1.dev1+g") def test_git_archive_export_ignore( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: wd.write("test1.txt", "test") wd.write("test2.txt", "test") wd.write( ".git/info/attributes", # Explicitly include test1.txt so that the test is not affected by # a potentially global gitattributes file on the test machine. "/test1.txt -export-ignore\n/test2.txt export-ignore", ) wd("git add test1.txt test2.txt") wd.commit() monkeypatch.chdir(wd.cwd) assert setuptools_scm._file_finders.find_files(".") == [opj(".", "test1.txt")] @pytest.mark.issue(228) def test_git_archive_subdirectory(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: os.mkdir(wd.cwd / "foobar") wd.write("foobar/test1.txt", "test") wd("git add foobar") wd.commit() monkeypatch.chdir(wd.cwd) assert setuptools_scm._file_finders.find_files(".") == [ opj(".", "foobar", "test1.txt") ] @pytest.mark.issue(251) def test_git_archive_run_from_subdirectory( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: os.mkdir(wd.cwd / "foobar") wd.write("foobar/test1.txt", "test") wd("git add foobar") wd.commit() monkeypatch.chdir(wd.cwd / "foobar") assert setuptools_scm._file_finders.find_files(".") == [opj(".", "test1.txt")] @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/728") def test_git_branch_names_correct(wd: WorkDir) -> None: wd.commit_testfile() wd("git checkout -b test/fun") wd_git = git.GitWorkdir(wd.cwd) assert wd_git.get_branch() == "test/fun" def test_git_feature_branch_increments_major(wd: WorkDir) -> None: wd.commit_testfile() wd("git tag 1.0.0") wd.commit_testfile() assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1") wd("git checkout -b feature/fun") wd.commit_testfile() assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0") @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/303") def test_not_matching_tags(wd: WorkDir) -> None: wd.commit_testfile() wd("git tag apache-arrow-0.11.1") wd.commit_testfile() wd("git tag apache-arrow-js-0.9.9") wd.commit_testfile() assert wd.get_version( tag_regex=r"^apache-arrow-([\.0-9]+)$", scm={ "git": { "describe_command": "git describe --dirty --tags --long --exclude *js* " } }, ).startswith("0.11.2") @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/411") def test_non_dotted_version(wd: WorkDir) -> None: wd.commit_testfile() wd("git tag apache-arrow-1") wd.commit_testfile() assert wd.get_version().startswith("2") def test_non_dotted_version_with_updated_regex(wd: WorkDir) -> None: wd.commit_testfile() wd("git tag apache-arrow-1") wd.commit_testfile() assert wd.get_version(tag_regex=r"^apache-arrow-([\.0-9]+)$").startswith("2") def test_non_dotted_tag_no_version_match(wd: WorkDir) -> None: wd.commit_testfile() wd("git tag apache-arrow-0.11.1") wd.commit_testfile() wd("git tag apache-arrow") wd.commit_testfile() assert wd.get_version().startswith("0.11.2.dev2") @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/381") def test_gitdir(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None: """ """ wd.commit_testfile() normal = wd.get_version() # git hooks set this and break subsequent setuptools-scm unless we clean monkeypatch.setenv("GIT_DIR", __file__) assert wd.get_version() == normal def test_git_getdate(wd: WorkDir) -> None: # TODO: case coverage for git wd parse today = datetime.now(timezone.utc).date() def parse_date() -> date: parsed = git.parse(os.fspath(wd.cwd), Configuration()) assert parsed is not None assert parsed.node_date is not None return parsed.node_date git_wd = git.GitWorkdir(wd.cwd) assert git_wd.get_head_date() is None assert parse_date() == today wd.commit_testfile() assert git_wd.get_head_date() == today assert parse_date() == today def test_git_getdate_badgit( wd: WorkDir, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch ) -> None: wd.commit_testfile() git_wd = git.GitWorkdir(wd.cwd) fake_date_result = CompletedProcess(args=[], stdout="%cI", stderr="", returncode=0) with patch.object( git, "run_git", Mock(return_value=fake_date_result), ): assert git_wd.get_head_date() is None def test_git_getdate_git_2_45_0_plus( wd: WorkDir, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch ) -> None: wd.commit_testfile() git_wd = git.GitWorkdir(wd.cwd) fake_date_result = CompletedProcess( args=[], stdout="2024-04-30T22:33:10Z", stderr="", returncode=0 ) with patch.object( git, "run_git", Mock(return_value=fake_date_result), ): assert git_wd.get_head_date() == date(2024, 4, 30) def test_git_getdate_timezone_consistency( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that get_head_date returns consistent UTC dates regardless of local timezone. This test forces a git commit with a timestamp that represents a time after midnight in a positive timezone offset but still the previous day in UTC. This is the exact scenario that was causing test failures in issue #1145. """ # Create a timestamp that's problematic: # - In Europe/Berlin (UTC+2): 2025-06-12 00:30:00 (June 12th) # - In UTC: 2025-06-11 22:30:00 (June 11th) problematic_timestamp = "2025-06-12T00:30:00+02:00" # Force git to use this specific timestamp for the commit monkeypatch.setenv("GIT_AUTHOR_DATE", problematic_timestamp) monkeypatch.setenv("GIT_COMMITTER_DATE", problematic_timestamp) wd.commit_testfile() git_wd = git.GitWorkdir(wd.cwd) result_date = git_wd.get_head_date() # The correct behavior is to return the UTC date (2025-06-11) # If the bug is present, it would return the timezone-local date (2025-06-12) expected_utc_date = date(2025, 6, 11) assert result_date == expected_utc_date @pytest.fixture def signed_commit_wd(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> WorkDir: if not has_command("gpg", args=["--version"], warn=False): pytest.skip("gpg executable not found") wd.write( ".gpg_batch_params", """\ %no-protection %transient-key Key-Type: RSA Key-Length: 2048 Name-Real: a test Name-Email: test@example.com Expire-Date: 0 """, ) monkeypatch.setenv("GNUPGHOME", str(wd.cwd.resolve(strict=True))) wd("gpg --batch --generate-key .gpg_batch_params") wd("git config log.showSignature true") wd.signed_commit_command = "git commit -S -m test-{reason}" return wd @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/548") def test_git_getdate_signed_commit(signed_commit_wd: WorkDir) -> None: today = datetime.now(timezone.utc).date() signed_commit_wd.commit_testfile(signed=True) git_wd = git.GitWorkdir(signed_commit_wd.cwd) assert git_wd.get_head_date() == today @pytest.mark.parametrize( ("expected", "from_data"), [ ( "1.0", {"describe-name": "1.0-0-g0000"}, ), ( "1.1.dev3+g0000", { "describe-name": "1.0-3-g0000", "node": "0" * 20, }, ), ("0.0", {"node": "0" * 20}), ("1.2.2", {"describe-name": "release-1.2.2-0-g00000"}), ("1.2.2.dev0", {"ref-names": "tag: release-1.2.2.dev"}), ("1.2.2", {"describe-name": "v1.2.2"}), ], ) @pytest.mark.filterwarnings("ignore:git archive did not support describe output") def test_git_archival_to_version(expected: str, from_data: dict[str, str]) -> None: config = Configuration( version_scheme="guess-next-dev", local_scheme="node-and-date" ) version = archival_to_version(from_data, config=config) assert version is not None assert format_version(version) == expected @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/727") def test_git_archival_node_missing_no_version() -> None: config = Configuration() version = archival_to_version({}, config=config) assert version is None def test_git_archival_from_unfiltered() -> None: config = Configuration() with pytest.warns( UserWarning, match=r"unprocessed git archival found \(no export subst applied\)" ): version = archival_to_version({"node": "$Format:%H$"}, config=config) assert version is None def test_fail_on_missing_submodules_no_gitmodules(wd: WorkDir) -> None: """Test that fail_on_missing_submodules does nothing when no .gitmodules exists.""" wd.commit_testfile() # Should not raise any exception git.fail_on_missing_submodules(git.GitWorkdir(wd.cwd)) def test_fail_on_missing_submodules_with_initialized_submodules(wd: WorkDir) -> None: """Test that fail_on_missing_submodules passes when submodules are initialized.""" # Create a submodule directory and .gitmodules file submodule_dir = wd.cwd / "external" submodule_dir.mkdir() # Initialize a git repo in the submodule directory wd(["git", "-C", str(submodule_dir), "init"]) wd(["git", "-C", str(submodule_dir), "config", "user.email", "test@example.com"]) wd(["git", "-C", str(submodule_dir), "config", "user.name", "Test User"]) # Create a commit in the submodule test_file = submodule_dir / "test.txt" test_file.write_text("test content", encoding="utf-8") wd(["git", "-C", str(submodule_dir), "add", "test.txt"]) wd(["git", "-C", str(submodule_dir), "commit", "-m", "Initial commit"]) # Add it as a submodule to the main repo wd(["git", "submodule", "add", str(submodule_dir), "external"]) wd.commit_testfile() # Should not raise any exception since the submodule is initialized git.fail_on_missing_submodules(git.GitWorkdir(wd.cwd)) def test_fail_on_missing_submodules_with_uninitialized_submodules( tmp_path: Path, ) -> None: """Test that fail_on_missing_submodules fails when submodules are not initialized.""" # Create a test repository with a .gitmodules file but no actual submodule test_repo = tmp_path / "test_repo" test_repo.mkdir() test_wd = setup_git_wd(WorkDir(test_repo)) # Create a fake .gitmodules file (this simulates what happens after cloning without --recurse-submodules) gitmodules_content = """[submodule "external"] path = external url = https://example.com/external.git """ test_wd.write(".gitmodules", gitmodules_content) test_wd.add_and_commit("Add-submodule-reference") # Should raise ValueError for uninitialized submodules with pytest.raises( ValueError, match=r"Submodules are defined in \.gitmodules but not initialized" ): git.fail_on_missing_submodules(git.GitWorkdir(test_repo)) def test_git_pre_parse_config_integration(wd: WorkDir) -> None: """Test that git_pre_parse configuration is used by the parse function.""" wd.commit_testfile() # Test with default (None) - should use warn_on_shallow config = Configuration() result = git.parse(str(wd.cwd), config) assert result is not None # Test with explicit configuration from setuptools_scm._config import GitConfiguration from setuptools_scm._config import ScmConfiguration config_with_pre_parse = Configuration( scm=ScmConfiguration( git=GitConfiguration(pre_parse=git.GitPreParse.WARN_ON_SHALLOW) ) ) result = git.parse(str(wd.cwd), config_with_pre_parse) assert result is not None # Test with different pre_parse value config_fail_shallow = Configuration( scm=ScmConfiguration( git=GitConfiguration(pre_parse=git.GitPreParse.FAIL_ON_MISSING_SUBMODULES) ) ) result = git.parse(str(wd.cwd), config_fail_shallow) assert result is not None def test_nested_scm_git_config_from_toml(tmp_path: Path) -> None: """Test that nested SCM git configuration is properly parsed from TOML.""" # Create a test pyproject.toml with nested SCM configuration pyproject_path = tmp_path / "pyproject.toml" pyproject_content = """ [tool.setuptools_scm.scm.git] pre_parse = "fail_on_missing_submodules" """ pyproject_path.write_text(pyproject_content, encoding="utf-8") # Parse the configuration from file config = Configuration.from_file(pyproject_path) # Verify the nested configuration was parsed correctly and converted to enum assert config.scm.git.pre_parse == git.GitPreParse.FAIL_ON_MISSING_SUBMODULES def test_nested_scm_git_config_from_data() -> None: """Test that nested SCM git configuration parsing works correctly with from_data.""" # Test configuration parsing directly without file I/O config_data = {"scm": {"git": {"pre_parse": "fail_on_missing_submodules"}}} # Parse the configuration data config = Configuration.from_data(relative_to=".", data=config_data) # Verify the nested configuration was parsed correctly and converted to enum assert config.scm.git.pre_parse == git.GitPreParse.FAIL_ON_MISSING_SUBMODULES def test_invalid_git_pre_parse_raises_error() -> None: """Test that invalid git pre_parse values raise a helpful ValueError.""" # Test configuration parsing directly without file I/O invalid_config_data = {"scm": {"git": {"pre_parse": "invalid_function"}}} # Parse the configuration data - should raise ValueError with pytest.raises( ValueError, match="Invalid git pre_parse function 'invalid_function'" ): Configuration.from_data(relative_to=".", data=invalid_config_data) def test_git_describe_command_backward_compatibility() -> None: """Test backward compatibility for git_describe_command configuration.""" # Test old configuration style still works with deprecation warning old_config_data = { "git_describe_command": "git describe --dirty --tags --long --exclude *js*" } with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"): config = Configuration.from_data(relative_to=".", data=old_config_data) # Verify it was migrated to the new location assert ( config.scm.git.describe_command == "git describe --dirty --tags --long --exclude *js*" ) def test_git_describe_command_from_data_conflict() -> None: """Test that specifying both old and new configuration in from_data raises ValueError.""" # Both old and new configuration specified - should raise ValueError mixed_config_data = { "git_describe_command": "old command", "scm": {"git": {"describe_command": "new command"}}, } # The Configuration constructor should handle the conflict detection with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"): with pytest.raises( ValueError, match=r"Cannot specify both.*git_describe_command" ): Configuration.from_data(relative_to=".", data=mixed_config_data) def test_git_describe_command_init_argument_deprecation() -> None: """Test that passing git_describe_command as init argument issues deprecation warning.""" # Test init argument with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"): config = Configuration(git_describe_command="test command") # Verify the value was migrated to the new location assert config.scm.git.describe_command == "test command" def test_git_describe_command_init_conflict() -> None: """Test that specifying both old and new configuration raises ValueError.""" from setuptools_scm._config import GitConfiguration from setuptools_scm._config import ScmConfiguration # Both old init arg and new configuration specified - should raise ValueError with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"): with pytest.raises( ValueError, match=r"Cannot specify both.*git_describe_command" ): Configuration( git_describe_command="old command", scm=ScmConfiguration( git=GitConfiguration(describe_command="new command") ), ) def test_git_no_commits_uses_fallback_version(wd: WorkDir) -> None: """Test that when git describe fails (no commits), fallback_version is used instead of 0.0.""" # Reinitialize as empty repo to remove any existing commits wd("rm -rf .git") wd("git init") wd("git config user.email test@example.com") wd('git config user.name "a test"') # Test with fallback_version set - should use the fallback instead of "0.0" config = Configuration(fallback_version="1.2.3") version = git.parse(str(wd.cwd), config) # Should get a version starting with the fallback version assert version is not None assert str(version.tag) == "1.2.3" assert version.distance == 0 assert version.dirty is True # No commits means dirty # Test without fallback_version - should default to "0.0" config_no_fallback = Configuration() version_no_fallback = git.parse(str(wd.cwd), config_no_fallback) assert version_no_fallback is not None assert str(version_no_fallback.tag) == "0.0" assert version_no_fallback.distance == 0 assert version_no_fallback.dirty is True setuptools-scm-9.2.2/testing/test_hg_git.py000066400000000000000000000067261507525030000210640ustar00rootroot00000000000000from __future__ import annotations import pytest from setuptools_scm import Configuration from setuptools_scm._run_cmd import CommandNotFoundError from setuptools_scm._run_cmd import has_command from setuptools_scm._run_cmd import run from setuptools_scm.hg import parse from testing.wd_wrapper import WorkDir @pytest.fixture(scope="module", autouse=True) def _check_hg_git() -> None: if not has_command("hg", warn=False): pytest.skip("hg executable not found") res = run("hg debuginstall --template {pythonexe}", cwd=".") if res.returncode: skip_no_hggit = True else: res = run([res.stdout, "-c", "import hggit"], cwd=".") skip_no_hggit = bool(res.returncode) if skip_no_hggit: pytest.skip("hg-git not installed") def test_base(repositories_hg_git: tuple[WorkDir, WorkDir]) -> None: wd, wd_git = repositories_hg_git assert wd_git.get_version() == "0.1.dev0+d20090213" assert wd.get_version() == "0.1.dev0+d20090213" wd_git.commit_testfile() version_git = wd_git.get_version() wd("hg pull -u") version = wd.get_version() assert version_git.startswith("0.1.dev1+g") assert version.startswith("0.1.dev1+g") assert not version_git.endswith("1-") assert not version.endswith("1-") wd_git("git tag v0.1") wd("hg pull -u") assert wd_git.get_version() == "0.1" assert wd.get_version() == "0.1" wd_git.write("test.txt", "test2") wd.write("test.txt", "test2") assert wd_git.get_version().startswith("0.2.dev0+g") assert wd.get_version().startswith("0.2.dev0+g") wd_git.commit_testfile() wd("hg pull") wd("hg up -C") assert wd_git.get_version().startswith("0.2.dev1+g") assert wd.get_version().startswith("0.2.dev1+g") wd_git("git tag version-0.2") wd("hg pull -u") assert wd_git.get_version().startswith("0.2") assert wd.get_version().startswith("0.2") wd_git.commit_testfile() wd_git("git tag version-0.2.post210+gbe48adfpost3+g0cc25f2") wd("hg pull -u") with pytest.warns( UserWarning, match="tag '.*' will be stripped of its suffix '.*'" ): assert wd_git.get_version().startswith("0.2") with pytest.warns( UserWarning, match="tag '.*' will be stripped of its suffix '.*'" ): assert wd.get_version().startswith("0.2") wd_git.commit_testfile() wd_git("git tag 17.33.0-rc") wd("hg pull -u") assert wd_git.get_version() == "17.33.0rc0" assert wd.get_version() == "17.33.0rc0" def test_hg_gone( repositories_hg_git: tuple[WorkDir, WorkDir], monkeypatch: pytest.MonkeyPatch ) -> None: wd = repositories_hg_git[0] monkeypatch.setenv("PATH", str(wd.cwd / "not-existing")) config = Configuration() wd.write("pyproject.toml", "[tool.setuptools_scm]") with pytest.raises(CommandNotFoundError, match=r"hg"): parse(wd.cwd, config=config) assert wd.get_version(fallback_version="1.0") == "1.0" def test_hg_command_from_env( repositories_hg_git: tuple[WorkDir, WorkDir], monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest, hg_exe: str, ) -> None: wd = repositories_hg_git[0] with monkeypatch.context() as m: m.setenv("SETUPTOOLS_SCM_HG_COMMAND", hg_exe) m.setenv("PATH", str(wd.cwd / "not-existing")) # No module reloading needed - runtime configuration works immediately wd.write("pyproject.toml", "[tool.setuptools_scm]") assert wd.get_version().startswith("0.1.dev0+") setuptools-scm-9.2.2/testing/test_integration.py000066400000000000000000000606571507525030000221510ustar00rootroot00000000000000from __future__ import annotations import importlib.metadata import logging import re import subprocess import sys import textwrap from pathlib import Path from typing import TYPE_CHECKING from typing import Any import pytest from packaging.version import Version from setuptools_scm._integration import setuptools as setuptools_integration from setuptools_scm._integration.pyproject_reading import PyProjectData from setuptools_scm._integration.setup_cfg import SetuptoolsBasicData from setuptools_scm._integration.setup_cfg import read_setup_cfg from setuptools_scm._requirement_cls import extract_package_name if TYPE_CHECKING: import setuptools from setuptools_scm import Configuration from setuptools_scm._integration.setuptools import _warn_on_old_setuptools from setuptools_scm._overrides import PRETEND_KEY from setuptools_scm._overrides import PRETEND_KEY_NAMED from setuptools_scm._run_cmd import run from .wd_wrapper import WorkDir c = Configuration() @pytest.fixture def wd(wd: WorkDir) -> WorkDir: wd("git init") wd("git config user.email test@example.com") wd('git config user.name "a test"') wd.add_command = "git add ." wd.commit_command = "git commit -m test-{reason}" return wd def test_pyproject_support(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") pkg = tmp_path / "package" pkg.mkdir() pkg.joinpath("pyproject.toml").write_text( textwrap.dedent( """ [tool.setuptools_scm] fallback_version = "12.34" [project] name = "foo" description = "Factory ⸻ A code generator 🏭" authors = [{name = "Łukasz Langa"}] dynamic = ["version"] """ ), encoding="utf-8", ) pkg.joinpath("setup.py").write_text( "__import__('setuptools').setup()", encoding="utf-8" ) res = run([sys.executable, "setup.py", "--version"], pkg) assert res.stdout == "12.34" def test_pretend_version(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None: monkeypatch.setenv(PRETEND_KEY, "1.0.0") assert wd.get_version() == "1.0.0" assert wd.get_version(dist_name="ignored") == "1.0.0" def test_pretend_version_named(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None: monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test".upper()), "1.0.0") monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test2".upper()), "2.0.0") assert wd.get_version(dist_name="test") == "1.0.0" assert wd.get_version(dist_name="test2") == "2.0.0" def test_pretend_version_name_takes_precedence( monkeypatch: pytest.MonkeyPatch, wd: WorkDir ) -> None: monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test".upper()), "1.0.0") monkeypatch.setenv(PRETEND_KEY, "2.0.0") assert wd.get_version(dist_name="test") == "1.0.0" def test_pretend_version_rejects_invalid_string( monkeypatch: pytest.MonkeyPatch, wd: WorkDir ) -> None: """Test that invalid pretend versions raise errors and bubble up.""" monkeypatch.setenv(PRETEND_KEY, "dummy") # With strict validation, invalid pretend versions should raise errors with pytest.raises(Exception, match=r".*dummy.*"): wd.get_version(write_to="test.py") def test_pretend_metadata_with_version( monkeypatch: pytest.MonkeyPatch, wd: WorkDir ) -> None: """Test pretend metadata overrides work with pretend version.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY monkeypatch.setenv(PRETEND_KEY, "1.2.3.dev4+g1337beef") monkeypatch.setenv(PRETEND_METADATA_KEY, '{node="g1337beef", distance=4}') version = wd.get_version() assert version == "1.2.3.dev4+g1337beef" # Test version file template functionality wd("mkdir -p src") version_file_content = """ version = '{version}' major = {version_tuple[0]} minor = {version_tuple[1]} patch = {version_tuple[2]} commit_hash = '{scm_version.short_node}' num_commit = {scm_version.distance} """ # noqa: RUF027 # Use write_to with template to create version file version = wd.get_version( write_to="src/version.py", write_to_template=version_file_content ) content = (wd.cwd / "src/version.py").read_text(encoding="utf-8") assert "commit_hash = 'g1337beef'" in content assert "num_commit = 4" in content def test_pretend_metadata_named(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None: """Test pretend metadata with named package support.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY_NAMED monkeypatch.setenv( PRETEND_KEY_NAMED.format(name="test".upper()), "1.2.3.dev5+gabcdef12" ) monkeypatch.setenv( PRETEND_METADATA_KEY_NAMED.format(name="test".upper()), '{node="gabcdef12", distance=5, dirty=true}', ) version = wd.get_version(dist_name="test") assert version == "1.2.3.dev5+gabcdef12" def test_pretend_metadata_without_version_warns( monkeypatch: pytest.MonkeyPatch, wd: WorkDir, caplog: pytest.LogCaptureFixture ) -> None: """Test that pretend metadata without any base version logs a warning.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY # Only set metadata, no version - but there will be a git repo so there will be a base version # Let's create an empty git repo without commits to truly have no base version monkeypatch.setenv(PRETEND_METADATA_KEY, '{node="g1234567", distance=2}') with caplog.at_level(logging.WARNING): version = wd.get_version() assert version is not None # In this case, metadata was applied to a fallback version, so no warning about missing base def test_pretend_metadata_with_scm_version( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that pretend metadata works with actual SCM-detected version.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY # Set up a git repo with a tag so we have a base version wd("git init") wd("git config user.name test") wd("git config user.email test@example.com") wd.write("file.txt", "content") wd("git add file.txt") wd("git commit -m 'initial'") wd("git tag v1.0.0") # Now add metadata overrides monkeypatch.setenv(PRETEND_METADATA_KEY, '{node="gcustom123", distance=7}') # Test that the metadata gets applied to the actual SCM version version = wd.get_version() # The version becomes 1.0.1.dev7+gcustom123 due to version scheme and metadata overrides assert "1.0.1.dev7+gcustom123" == version # Test version file to see if metadata was applied wd("mkdir -p src") version_file_content = """ version = '{version}' commit_hash = '{scm_version.short_node}' num_commit = {scm_version.distance} """ # noqa: RUF027 version = wd.get_version( write_to="src/version.py", write_to_template=version_file_content ) content = (wd.cwd / "src/version.py").read_text(encoding="utf-8") assert "commit_hash = 'gcustom123'" in content assert "num_commit = 7" in content def test_pretend_metadata_type_conversion( monkeypatch: pytest.MonkeyPatch, wd: WorkDir ) -> None: """Test that pretend metadata properly uses TOML native types.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY monkeypatch.setenv(PRETEND_KEY, "2.0.0") monkeypatch.setenv( PRETEND_METADATA_KEY, '{distance=10, dirty=true, node="gfedcba98", branch="feature-branch"}', ) version = wd.get_version() # The version should be formatted properly with the metadata assert "2.0.0" in version def test_pretend_metadata_invalid_fields_filtered( monkeypatch: pytest.MonkeyPatch, wd: WorkDir, caplog: pytest.LogCaptureFixture ) -> None: """Test that invalid metadata fields are filtered out with a warning.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY monkeypatch.setenv(PRETEND_KEY, "1.0.0") monkeypatch.setenv( PRETEND_METADATA_KEY, '{node="g123456", distance=3, invalid_field="should_be_ignored", another_bad_field=42}', ) with caplog.at_level(logging.WARNING): version = wd.get_version() assert version == "1.0.0" assert "Invalid metadata fields in pretend metadata" in caplog.text assert "invalid_field" in caplog.text assert "another_bad_field" in caplog.text def test_pretend_metadata_date_parsing( monkeypatch: pytest.MonkeyPatch, wd: WorkDir ) -> None: """Test that TOML date values work in pretend metadata.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY monkeypatch.setenv(PRETEND_KEY, "1.5.0") monkeypatch.setenv( PRETEND_METADATA_KEY, '{node="g987654", distance=7, node_date=2024-01-15}' ) version = wd.get_version() assert version == "1.5.0" def test_pretend_metadata_invalid_toml_error( monkeypatch: pytest.MonkeyPatch, wd: WorkDir, caplog: pytest.LogCaptureFixture ) -> None: """Test that invalid TOML in pretend metadata logs an error.""" from setuptools_scm._overrides import PRETEND_METADATA_KEY monkeypatch.setenv(PRETEND_KEY, "1.0.0") monkeypatch.setenv(PRETEND_METADATA_KEY, "{invalid toml syntax here}") with caplog.at_level(logging.ERROR): version = wd.get_version() # Should fall back to basic pretend version assert version == "1.0.0" assert "Failed to parse pretend metadata" in caplog.text def test_git_tag_with_local_build_data_preserved(wd: WorkDir) -> None: """Test that git tags containing local build data are preserved in final version.""" wd.commit_testfile() # Create a git tag that includes local build data # This simulates a CI system that creates tags with build metadata wd("git tag 1.0.0+build.123") # The version should preserve the build metadata from the tag version = wd.get_version() # Validate it's a proper PEP 440 version parsed_version = Version(version) assert str(parsed_version) == version, ( f"Version should parse correctly as PEP 440: {version}" ) assert version == "1.0.0+build.123", ( f"Expected build metadata preserved, got {version}" ) # Validate the local part is correct assert parsed_version.local == "build.123", ( f"Expected local part 'build.123', got {parsed_version.local}" ) def test_git_tag_with_commit_hash_preserved(wd: WorkDir) -> None: """Test that git tags with commit hash data are preserved.""" wd.commit_testfile() # Create a git tag that includes commit hash metadata wd("git tag 2.0.0+sha.abcd1234") # The version should preserve the commit hash from the tag version = wd.get_version() # Validate it's a proper PEP 440 version parsed_version = Version(version) assert str(parsed_version) == version, ( f"Version should parse correctly as PEP 440: {version}" ) assert version == "2.0.0+sha.abcd1234" # Validate the local part is correct assert parsed_version.local == "sha.abcd1234", ( f"Expected local part 'sha.abcd1234', got {parsed_version.local}" ) def test_git_tag_with_local_build_data_preserved_dirty_workdir(wd: WorkDir) -> None: """Test that git tags with local build data are preserved even with dirty working directory.""" wd.commit_testfile() # Create a git tag that includes local build data wd("git tag 1.5.0+build.456") # Make working directory dirty wd.write("modified_file.txt", "some changes") # The version should preserve the build metadata from the tag # even when working directory is dirty version = wd.get_version() # Validate it's a proper PEP 440 version parsed_version = Version(version) assert str(parsed_version) == version, ( f"Version should parse correctly as PEP 440: {version}" ) assert version == "1.5.0+build.456", ( f"Expected build metadata preserved with dirty workdir, got {version}" ) # Validate the local part is correct assert parsed_version.local == "build.456", ( f"Expected local part 'build.456', got {parsed_version.local}" ) def test_git_tag_with_local_build_data_preserved_with_distance(wd: WorkDir) -> None: """Test that git tags with local build data are preserved with distance.""" wd.commit_testfile() # Create a git tag that includes local build data wd("git tag 3.0.0+ci.789") # Add another commit after the tag to create distance wd.commit_testfile("after-tag") # The version should use version scheme for distance but preserve original tag's build data version = wd.get_version() # Validate it's a proper PEP 440 version parsed_version = Version(version) assert str(parsed_version) == version, ( f"Version should parse correctly as PEP 440: {version}" ) # Tag local data should be preserved and combined with SCM data assert version.startswith("3.0.1.dev1"), ( f"Expected dev version with distance, got {version}" ) # Use regex to validate the version format with both tag build data and SCM node data # Expected format: 3.0.1.dev1+ci.789.g version_pattern = r"^3\.0\.1\.dev1\+ci\.789\.g[a-f0-9]+$" assert re.match(version_pattern, version), ( f"Version should match pattern {version_pattern}, got {version}" ) # The original tag's local data (+ci.789) should be preserved and combined with SCM data assert "+ci.789" in version, f"Tag local data should be preserved, got {version}" # Validate the local part contains both tag and SCM node information assert parsed_version.local is not None, ( f"Expected local version part, got {parsed_version.local}" ) assert "ci.789" in parsed_version.local, ( f"Expected local part to contain tag data 'ci.789', got {parsed_version.local}" ) assert "g" in parsed_version.local, ( f"Expected local part to contain SCM node data 'g...', got {parsed_version.local}" ) # Note: This test verifies that local build data from tags is preserved and combined # with SCM data when there's distance, which is the desired behavior for issue 1019. def testwarn_on_broken_setuptools() -> None: _warn_on_old_setuptools("61") with pytest.warns(RuntimeWarning, match="ERROR: setuptools==60"): _warn_on_old_setuptools("60") @pytest.mark.issue(611) def test_distribution_provides_extras() -> None: from importlib.metadata import distribution dist = distribution("setuptools_scm") pe: list[str] = dist.metadata.get_all("Provides-Extra", []) assert sorted(pe) == ["rich", "simple", "toml"] @pytest.mark.issue(760) def test_unicode_in_setup_cfg(tmp_path: Path) -> None: cfg = tmp_path / "setup.cfg" cfg.write_text( textwrap.dedent( """ [metadata] name = configparser author = Łukasz Langa """ ), encoding="utf-8", ) from setuptools_scm._integration.setup_cfg import read_setup_cfg name = read_setup_cfg(cfg).name assert name == "configparser" # also ensure we can parse a version if present (legacy projects) cfg.write_text( textwrap.dedent( """ [metadata] name = configparser version = 1.2.3 """ ), encoding="utf-8", ) data = read_setup_cfg(cfg) assert isinstance(data, SetuptoolsBasicData) assert data.name == "configparser" assert data.version == "1.2.3" @pytest.mark.issue(1216) def test_setup_cfg_dynamic_version_warns_and_ignores(tmp_path: Path) -> None: cfg = tmp_path / "setup.cfg" cfg.write_text( textwrap.dedent( """ [metadata] name = example-broken version = attr: example_broken.__version__ """ ), encoding="utf-8", ) with pytest.warns( UserWarning, match=r"setup\.cfg: at \[metadata\]", ): legacy_data = read_setup_cfg(cfg) assert legacy_data.version is None def test_setup_cfg_version_prevents_inference_version_keyword( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: # Legacy project setup - we construct the data directly since files are not read anyway monkeypatch.chdir(tmp_path) dist = create_clean_distribution("legacy-proj") # Using keyword should detect an existing version via legacy data and avoid inferring from setuptools_scm._integration import setuptools as setuptools_integration from setuptools_scm._integration.pyproject_reading import PyProjectData from setuptools_scm._integration.setup_cfg import SetuptoolsBasicData # Construct PyProjectData directly without requiring build backend inference pyproject_data = PyProjectData.for_testing( is_required=False, # setuptools-scm not required section_present=False, # no [tool.setuptools_scm] section project_present=False, # no [project] section ) # Construct legacy data with version from setup.cfg legacy_data = SetuptoolsBasicData( path=tmp_path / "setup.cfg", name="legacy-proj", version="0.9.0" ) with pytest.warns(UserWarning, match="version of legacy-proj already set"): setuptools_integration.version_keyword( dist, "use_scm_version", True, _given_pyproject_data=pyproject_data, _given_legacy_data=legacy_data, ) # setuptools_scm should not set a version when setup.cfg already provided one assert dist.metadata.version is None def test_setuptools_version_keyword_ensures_regex( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, ) -> None: wd.commit_testfile("test") wd("git tag 1.0") monkeypatch.chdir(wd.cwd) dist = create_clean_distribution("test") setuptools_integration.version_keyword( dist, "use_scm_version", {"tag_regex": "(1.0)"} ) assert dist.metadata.version == "1.0" @pytest.mark.parametrize( "ep_name", ["setuptools_scm.parse_scm", "setuptools_scm.parse_scm_fallback"] ) def test_git_archival_plugin_ignored(tmp_path: Path, ep_name: str) -> None: tmp_path.joinpath(".git_archival.txt").write_text("broken", encoding="utf-8") try: dist = importlib.metadata.distribution("setuptools_scm_git_archive") except importlib.metadata.PackageNotFoundError: pytest.skip("setuptools_scm_git_archive not installed") else: print(dist.metadata["Name"], dist.version) from setuptools_scm.discover import iter_matching_entrypoints found = list(iter_matching_entrypoints(tmp_path, config=c, entrypoint=ep_name)) imports = [item.value for item in found] assert "setuptools_scm_git_archive:parse" not in imports @pytest.mark.parametrize("base_name", ["setuptools_scm", "setuptools-scm"]) @pytest.mark.parametrize( "requirements", ["", ">=8", "[toml]>=7", "~=9.0", "[rich,toml]>=8"], ids=["empty", "version", "extras", "fuzzy", "multiple-extras"], ) def test_extract_package_name(base_name: str, requirements: str) -> None: """Test the _extract_package_name helper function""" assert extract_package_name(f"{base_name}{requirements}") == "setuptools-scm" # Helper function for creating and managing distribution objects def create_clean_distribution(name: str) -> setuptools.Distribution: """Create a clean distribution object without any setuptools_scm effects. This function creates a new setuptools Distribution and ensures it's completely clean from any previous setuptools_scm version inference effects, including: - Clearing any existing version - Removing the _setuptools_scm_version_set_by_infer flag """ import setuptools dist = setuptools.Distribution({"name": name}) # Clean all setuptools_scm effects dist.metadata.version = None if hasattr(dist, "_setuptools_scm_version_set_by_infer"): delattr(dist, "_setuptools_scm_version_set_by_infer") return dist def version_keyword_default( dist: setuptools.Distribution, pyproject_data: PyProjectData | None = None ) -> None: """Helper to call version_keyword with default config and return the result.""" setuptools_integration.version_keyword( dist, "use_scm_version", True, _given_pyproject_data=pyproject_data ) def version_keyword_calver( dist: setuptools.Distribution, pyproject_data: PyProjectData | None = None ) -> None: """Helper to call version_keyword with calver-by-date scheme and return the result.""" setuptools_integration.version_keyword( dist, "use_scm_version", {"version_scheme": "calver-by-date"}, _given_pyproject_data=pyproject_data, ) def infer_version_with_data( dist: setuptools.Distribution, pyproject_data: PyProjectData | None = None ) -> None: """Helper to call infer_version with pyproject data.""" setuptools_integration.infer_version(dist, _given_pyproject_data=pyproject_data) @pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/1022") @pytest.mark.filterwarnings("ignore:version of .* already set:UserWarning") @pytest.mark.filterwarnings( "ignore:.* does not correspond to a valid versioning date.*:UserWarning" ) @pytest.mark.parametrize( ("first_integration", "second_integration", "expected_final_version"), [ # infer_version and version_keyword can be called in either order (infer_version_with_data, version_keyword_default, "1.0.1.dev1"), (infer_version_with_data, version_keyword_calver, "9.2.13.0.dev1"), (version_keyword_default, infer_version_with_data, "1.0.1.dev1"), (version_keyword_calver, infer_version_with_data, "9.2.13.0.dev1"), ], ) def test_integration_function_call_order( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, first_integration: Any, second_integration: Any, expected_final_version: str, ) -> None: """Test that integration functions can be called in any order. version_keyword should always win when it specifies configuration, but currently doesn't. Some tests will fail, showing the bug. """ # Set up controlled environment for deterministic versions monkeypatch.setenv("SOURCE_DATE_EPOCH", "1234567890") # 2009-02-13T23:31:30+00:00 # Override node_date to get consistent calver versions monkeypatch.setenv( "SETUPTOOLS_SCM_PRETEND_METADATA_FOR_TEST_CALL_ORDER", "{node_date=2009-02-13}" ) # Set up a git repository with a tag and known commit hash wd.commit_testfile("test") wd("git tag 1.0.0") wd.commit_testfile("test2") # Add another commit to get distance monkeypatch.chdir(wd.cwd) # Create PyProjectData with equivalent configuration - no file I/O! project_name = "test-call-order" pyproject_data = PyProjectData.for_testing( project_name=project_name, has_dynamic_version=True, project_present=True, section_present=True, local_scheme="no-local-version", ) dist = create_clean_distribution(project_name) # Call both integration functions in order with direct data injection first_integration(dist, pyproject_data) second_integration(dist, pyproject_data) # Get the final version directly from the distribution final_version = dist.metadata.version # Assert the final version matches expectation # Some tests will fail here, demonstrating the bug where version_keyword doesn't override assert final_version == expected_final_version, ( f"Expected version '{expected_final_version}' but got '{final_version}'" ) @pytest.mark.issue("xmlsec-regression") def test_xmlsec_download_regression( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Test that pip download works for xmlsec package without causing setuptools_scm regression. This test ensures that downloading and building xmlsec from source doesn't fail due to setuptools_scm issues when using --no-build-isolation. """ # Set up environment with setuptools_scm debug enabled monkeypatch.setenv("SETUPTOOLS_SCM_DEBUG", "1") monkeypatch.setenv("COLUMNS", "150") # Run pip download command with no-binary and no-build-isolation try: subprocess.run( [ *(sys.executable, "-m", "pip", "download"), *("--no-binary", "xmlsec"), "--no-build-isolation", "-v", "xmlsec==1.3.16", ], cwd=tmp_path, timeout=300, check=True, ) except subprocess.CalledProcessError as e: pytest.fail(f"pip download failed: {e}", pytrace=False) # The success of the subprocess.run call above means the regression is fixed. # pip download succeeded without setuptools_scm causing version conflicts. setuptools-scm-9.2.2/testing/test_internal_log_level.py000066400000000000000000000007371507525030000234630ustar00rootroot00000000000000from __future__ import annotations import logging from setuptools_scm import _log def test_log_levels_when_set() -> None: assert _log._default_log_level({"SETUPTOOLS_SCM_DEBUG": ""}) == logging.DEBUG assert _log._default_log_level({"SETUPTOOLS_SCM_DEBUG": "INFO"}) == logging.DEBUG assert _log._default_log_level({"SETUPTOOLS_SCM_DEBUG": "3"}) == logging.DEBUG def test_log_levels_when_unset() -> None: assert _log._default_log_level({}) == logging.WARNING setuptools-scm-9.2.2/testing/test_main.py000066400000000000000000000032011507525030000205300ustar00rootroot00000000000000from __future__ import annotations import sys import textwrap from pathlib import Path import pytest from .wd_wrapper import WorkDir def test_main() -> None: mainfile = Path(__file__).parent.parent.joinpath( "src", "setuptools_scm", "__main__.py" ) ns = {"__package__": "setuptools_scm"} code = compile(mainfile.read_text(encoding="utf-8"), "__main__.py", "exec") exec(code, ns) @pytest.fixture def repo(wd: WorkDir) -> WorkDir: wd("git init") wd("git config user.email user@host") wd("git config user.name user") wd.add_command = "git add ." wd.commit_command = "git commit -m test-{reason}" wd.write("README.rst", "My example") wd.add_and_commit() wd("git tag v0.1.0") wd.write("file.txt", "file.txt") wd.add_and_commit() return wd def test_repo_with_config(repo: WorkDir) -> None: pyproject = """\ [tool.setuptools_scm] version_scheme = "no-guess-dev" [project] name = "example" """ repo.write("pyproject.toml", textwrap.dedent(pyproject)) repo.add_and_commit() res = repo([sys.executable, "-m", "setuptools_scm"]) assert res.startswith("0.1.0.post1.dev2") def test_repo_without_config(repo: WorkDir) -> None: res = repo([sys.executable, "-m", "setuptools_scm"]) assert res.startswith("0.1.1.dev1") def test_repo_with_pyproject_missing_setuptools_scm(repo: WorkDir) -> None: pyproject = """\ [project] name = "example" """ repo.write("pyproject.toml", textwrap.dedent(pyproject)) repo.add_and_commit() res = repo([sys.executable, "-m", "setuptools_scm"]) assert res.startswith("0.1.1.dev2") setuptools-scm-9.2.2/testing/test_mercurial.py000066400000000000000000000162341507525030000216010ustar00rootroot00000000000000from __future__ import annotations import os from pathlib import Path import pytest import setuptools_scm._file_finders from setuptools_scm import Configuration from setuptools_scm._run_cmd import CommandNotFoundError from setuptools_scm._run_cmd import has_command from setuptools_scm.hg import archival_to_version from setuptools_scm.hg import parse from setuptools_scm.version import format_version from testing.wd_wrapper import WorkDir pytestmark = pytest.mark.skipif( not has_command("hg", warn=False), reason="hg executable not found" ) @pytest.fixture def wd(wd: WorkDir) -> WorkDir: wd("hg init") wd.add_command = "hg add ." wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"' return wd archival_mapping = { "1.0": {"tag": "1.0"}, "1.1.0.dev3+h0000000000": { "latesttag": "1.0", "latesttagdistance": "3", "node": "0" * 20, }, "1.0.1.dev3+h0000000000": { "latesttag": "1.0.0", "latesttagdistance": "3", "branch": "1.0", "node": "0" * 20, }, "0.0": {"node": "0" * 20}, "1.2.2": {"tag": "release-1.2.2"}, "1.2.2.dev0": {"tag": "release-1.2.2.dev"}, } @pytest.mark.parametrize(("expected", "data"), sorted(archival_mapping.items())) def test_archival_to_version(expected: str, data: dict[str, str]) -> None: config = Configuration( version_scheme="release-branch-semver", local_scheme="node-and-date" ) version = archival_to_version(data, config=config) assert format_version(version) == expected def test_hg_gone(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PATH", str(wd.cwd / "not-existing")) config = Configuration() wd.write("pyproject.toml", "[tool.setuptools_scm]") with pytest.raises(CommandNotFoundError, match=r"hg"): parse(wd.cwd, config=config) assert wd.get_version(fallback_version="1.0") == "1.0" def test_hg_command_from_env( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest, hg_exe: str, ) -> None: wd.write("pyproject.toml", "[tool.setuptools_scm]") # Need to commit something first for versioning to work wd.commit_testfile() monkeypatch.setenv("SETUPTOOLS_SCM_HG_COMMAND", hg_exe) monkeypatch.setenv("PATH", str(wd.cwd / "not-existing")) version = wd.get_version() assert version.startswith("0.1.dev1+") def test_hg_command_from_env_is_invalid( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest ) -> None: with monkeypatch.context() as m: m.setenv("SETUPTOOLS_SCM_HG_COMMAND", str(wd.cwd / "not-existing")) # No module reloading needed - runtime configuration works immediately config = Configuration() wd.write("pyproject.toml", "[tool.setuptools_scm]") with pytest.raises(CommandNotFoundError, match=r"test.*hg.*not-existing"): parse(wd.cwd, config=config) assert wd.get_version(fallback_version="1.0") == "1.0" def test_find_files_stop_at_root_hg( wd: WorkDir, monkeypatch: pytest.MonkeyPatch ) -> None: wd.commit_testfile() project = wd.cwd / "project" project.mkdir() project.joinpath("setup.cfg").touch() # setup.cfg has not been committed assert setuptools_scm._file_finders.find_files(str(project)) == [] # issue 251 wd.add_and_commit() monkeypatch.chdir(project) assert setuptools_scm._file_finders.find_files() == ["setup.cfg"] # XXX: better tests for tag prefixes def test_version_from_hg_id(wd: WorkDir) -> None: assert wd.get_version() == "0.0" wd.commit_testfile() assert wd.get_version().startswith("0.1.dev1+") # tagging commit is considered the tag wd('hg tag v0.1 -u test -d "0 0"') assert wd.get_version() == "0.1" wd.commit_testfile() assert wd.get_version().startswith("0.2.dev2") wd("hg up v0.1") assert wd.get_version() == "0.1" # commit originating from the tagged revision # that is not an actual tag wd.commit_testfile() assert wd.get_version().startswith("0.2.dev1+") # several tags wd("hg up") wd('hg tag v0.2 -u test -d "0 0"') wd('hg tag v0.3 -u test -d "0 0" -r v0.2') assert wd.get_version() == "0.3" def test_version_from_archival(wd: WorkDir) -> None: # entrypoints are unordered, # cleaning the wd ensure this test won't break randomly wd.cwd.joinpath(".hg").rename(wd.cwd / ".nothg") wd.write( ".hg_archival.txt", """\ node: 000000000000 tag: 0.1 """, ) assert wd.get_version() == "0.1" wd.write( ".hg_archival.txt", """\ node: 000000000000 latesttag: 0.1 latesttagdistance: 3 """, ) assert wd.get_version() == "0.2.dev3+h0000000000" @pytest.mark.issue("#72") def test_version_in_merge(wd: WorkDir) -> None: wd.commit_testfile() wd.commit_testfile() wd("hg up 0") wd.commit_testfile() wd("hg merge --tool :merge") assert wd.get_version() is not None @pytest.mark.issue(128) def test_parse_no_worktree(tmp_path: Path) -> None: config = Configuration() ret = parse(os.fspath(tmp_path), config) assert ret is None @pytest.fixture def version_1_0(wd: WorkDir) -> WorkDir: wd("hg branch default") wd.commit_testfile() wd('hg tag 1.0.0 -u test -d "0 0"') return wd @pytest.fixture def pre_merge_commit_after_tag(version_1_0: WorkDir) -> WorkDir: wd = version_1_0 wd("hg branch testbranch") wd.write("branchfile", "branchtext") wd(wd.add_command) wd.commit() wd("hg update default") wd("hg merge testbranch") return wd @pytest.mark.usefixtures("pre_merge_commit_after_tag") def test_version_bump_before_merge_commit(wd: WorkDir) -> None: assert wd.get_version().startswith("1.0.1.dev1+") @pytest.mark.issue(219) @pytest.mark.usefixtures("pre_merge_commit_after_tag") def test_version_bump_from_merge_commit(wd: WorkDir) -> None: wd.commit() assert wd.get_version().startswith("1.0.1.dev3+") # issue 219 @pytest.mark.usefixtures("version_1_0") def test_version_bump_from_commit_including_hgtag_mods(wd: WorkDir) -> None: """Test the case where a commit includes changes to .hgtags and other files""" with wd.cwd.joinpath(".hgtags").open("ab") as tagfile: tagfile.write(b"0 0\n") wd.write("branchfile", "branchtext") wd(wd.add_command) assert wd.get_version().startswith("1.0.1.dev1+") # bump from dirty version wd.commit() # commits both the testfile _and_ .hgtags assert wd.get_version().startswith("1.0.1.dev2+") @pytest.mark.issue(229) @pytest.mark.usefixtures("version_1_0") def test_latest_tag_detection(wd: WorkDir) -> None: """Tests that tags not containing a "." are ignored, the same as for git. Note that will be superseded by the fix for pypa/setuptools-scm/issues/235 """ wd('hg tag some-random-tag -u test -d "0 0"') assert wd.get_version() == "1.0.0" @pytest.mark.usefixtures("version_1_0") def test_feature_branch_increments_major(wd: WorkDir) -> None: wd.commit_testfile() assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1") wd("hg branch feature/fun") assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0") setuptools-scm-9.2.2/testing/test_overrides.py000066400000000000000000000224231507525030000216150ustar00rootroot00000000000000from __future__ import annotations import logging import pytest from setuptools_scm._overrides import _find_close_env_var_matches from setuptools_scm._overrides import _search_env_vars_with_prefix from setuptools_scm._overrides import read_named_env class TestSearchEnvVarsWithPrefix: """Test the _search_env_vars_with_prefix helper function.""" def test_exact_match(self) -> None: """Test finding exact normalized matches.""" env = {"SETUPTOOLS_SCM_TEST_FOR_MY_PACKAGE": "value1"} matches = _search_env_vars_with_prefix( "SETUPTOOLS_SCM_TEST_FOR_", "my-package", env ) assert len(matches) == 1 assert matches[0] == ("SETUPTOOLS_SCM_TEST_FOR_MY_PACKAGE", "value1") def test_multiple_normalizations(self) -> None: """Test finding various normalization patterns.""" # Set up different normalization patterns env = { "SETUPTOOLS_SCM_TEST_FOR_MY_AWESOME_PKG": "value1", "SETUPTOOLS_SCM_TEST_FOR_MYAWESOMEPKG": "value2", "SETUPTOOLS_SCM_TEST_FOR_MY_AWESOME-PKG": "value3", # duplicate } matches = _search_env_vars_with_prefix( "SETUPTOOLS_SCM_TEST_FOR_", "my-awesome.pkg", env ) # Should find the variants that match our normalization patterns assert len(matches) >= 1 env_vars = [var for var, _ in matches] assert "SETUPTOOLS_SCM_TEST_FOR_MY_AWESOME_PKG" in env_vars def test_no_matches(self) -> None: """Test when no matches are found.""" # Set up unrelated env vars env = { "OTHER_VAR": "value", "SETUPTOOLS_SCM_OTHER_FOR_SOMETHING": "value", } matches = _search_env_vars_with_prefix( "SETUPTOOLS_SCM_TEST_FOR_", "nonexistent", env ) assert len(matches) == 0 def test_case_variations(self) -> None: """Test that case variations are handled.""" env = {"SETUPTOOLS_SCM_TEST_FOR_MYPACKAGE": "value1"} matches = _search_env_vars_with_prefix( "SETUPTOOLS_SCM_TEST_FOR_", "MyPackage", env ) assert len(matches) == 1 assert matches[0][1] == "value1" class TestFindCloseEnvVarMatches: """Test the _find_close_env_var_matches helper function.""" def test_close_matches(self) -> None: """Test finding close matches for potential typos.""" env = { "SETUPTOOLS_SCM_TEST_FOR_MY_PACKAG": "typo1", # missing 'e' "SETUPTOOLS_SCM_TEST_FOR_MY_PAKAGE": "typo2", # 'c' -> 'k' "SETUPTOOLS_SCM_TEST_FOR_OTHER_PKG": "unrelated", } close_matches = _find_close_env_var_matches( "SETUPTOOLS_SCM_TEST_FOR_", "MY_PACKAGE", env ) # Should find the close matches but not the unrelated one assert "SETUPTOOLS_SCM_TEST_FOR_MY_PACKAG" in close_matches assert "SETUPTOOLS_SCM_TEST_FOR_MY_PAKAGE" in close_matches assert "SETUPTOOLS_SCM_TEST_FOR_OTHER_PKG" not in close_matches def test_threshold(self) -> None: """Test that threshold filtering works.""" env = {"SETUPTOOLS_SCM_TEST_FOR_COMPLETELY_DIFFERENT": "unrelated"} close_matches = _find_close_env_var_matches( "SETUPTOOLS_SCM_TEST_FOR_", "MY_PACKAGE", env, threshold=0.8 ) # With high threshold, completely different string shouldn't match assert len(close_matches) == 0 def test_no_close_matches(self) -> None: """Test when no close matches exist.""" env: dict[str, str] = {} close_matches = _find_close_env_var_matches( "SETUPTOOLS_SCM_TEST_FOR_", "MY_PACKAGE", env ) assert len(close_matches) == 0 class TestReadNamedEnvEnhanced: """Test the enhanced read_named_env function.""" def test_standard_behavior_unchanged(self) -> None: """Test that standard behavior still works.""" # Generic env var env = {"SETUPTOOLS_SCM_TEST": "generic_value"} assert read_named_env(name="TEST", dist_name=None, env=env) == "generic_value" # Dist-specific env var (standard normalization) env = {"SETUPTOOLS_SCM_TEST_FOR_MY_PACKAGE": "specific_value"} assert ( read_named_env(name="TEST", dist_name="my-package", env=env) == "specific_value" ) def test_alternative_normalization_found( self, caplog: pytest.LogCaptureFixture ) -> None: """Test finding alternative normalizations with warnings.""" # Set up an alternative normalization pattern (user uses dots instead of canonical hyphens) env = {"SETUPTOOLS_SCM_TEST_FOR_MY.PACKAGE": "alt_value"} with caplog.at_level(logging.WARNING): result = read_named_env(name="TEST", dist_name="my.package", env=env) assert result == "alt_value" assert "Found environment variable" in caplog.text assert "but expected" in caplog.text def test_multiple_alternatives_warning( self, caplog: pytest.LogCaptureFixture ) -> None: """Test warning when multiple alternative normalizations exist.""" # Set up multiple alternatives that represent the same canonical package name # but use different normalizations in the env var env = { "SETUPTOOLS_SCM_TEST_FOR_MY.PACKAGE": "alt1", # dots instead of hyphens "SETUPTOOLS_SCM_TEST_FOR_MY-PACKAGE": "alt2", # dashes instead of underscores "SETUPTOOLS_SCM_TEST_FOR_my.package": "alt3", # lowercase } with caplog.at_level(logging.WARNING): result = read_named_env(name="TEST", dist_name="my.package", env=env) assert result in ["alt1", "alt2", "alt3"] # Should use one of them assert "Multiple alternative environment variables found" in caplog.text def test_typo_suggestions(self, caplog: pytest.LogCaptureFixture) -> None: """Test suggestions for potential typos.""" # Set up a close but not exact match env = {"SETUPTOOLS_SCM_TEST_FOR_MY_PACKAG": "typo_value"} with caplog.at_level(logging.WARNING): result = read_named_env(name="TEST", dist_name="my-package", env=env) # Should return None (generic fallback) but warn about close matches assert result is None assert "Did you mean one of these?" in caplog.text assert "SETUPTOOLS_SCM_TEST_FOR_MY_PACKAG" in caplog.text def test_fallback_to_generic(self) -> None: """Test fallback to generic env var when dist-specific not found.""" env = {"SETUPTOOLS_SCM_TEST": "generic_fallback"} result = read_named_env(name="TEST", dist_name="nonexistent-package", env=env) assert result == "generic_fallback" def test_no_generic_fallback(self) -> None: """Test behavior when neither dist-specific nor generic env vars exist.""" env: dict[str, str] = {} result = read_named_env(name="TEST", dist_name="some-package", env=env) assert result is None def test_dist_specific_overrides_generic(self) -> None: """Test that dist-specific env vars override generic ones.""" env = { "SETUPTOOLS_SCM_TEST": "generic", "SETUPTOOLS_SCM_TEST_FOR_MY_PACKAGE": "specific", } result = read_named_env(name="TEST", dist_name="my-package", env=env) assert result == "specific" def test_custom_tool_prefix(self) -> None: """Test that custom tool prefixes work.""" env = {"CUSTOM_TOOL_TEST_FOR_MY_PACKAGE": "custom_value"} result = read_named_env( tool="CUSTOM_TOOL", name="TEST", dist_name="my-package", env=env ) assert result == "custom_value" def test_complex_dist_name_normalization( self, caplog: pytest.LogCaptureFixture ) -> None: """Test complex dist name normalization scenarios.""" # User uses a non-canonical format (keeping underscores instead of canonical hyphens) # The canonical form of "complex.dist-name_with.dots" is "complex-dist-name-with-dots" # which becomes "COMPLEX_DIST_NAME_WITH_DOTS" as env var # But user set it with mixed format: env = {"SETUPTOOLS_SCM_TEST_FOR_COMPLEX.DIST_NAME_WITH.DOTS": "value"} with caplog.at_level(logging.WARNING): result = read_named_env( name="TEST", dist_name="complex.dist-name_with.dots", env=env ) assert result == "value" assert "Found environment variable" in caplog.text def test_lowercase_environment_variable( self, caplog: pytest.LogCaptureFixture ) -> None: """Test that lowercase environment variables are found as alternatives.""" env = {"SETUPTOOLS_SCM_TEST_FOR_my.package": "lowercase_value"} with caplog.at_level(logging.WARNING): result = read_named_env(name="TEST", dist_name="my.package", env=env) assert result == "lowercase_value" assert "Found environment variable" in caplog.text assert "but expected" in caplog.text def test_edge_case_empty_dist_name(self) -> None: """Test edge case with empty dist name.""" env = {"SETUPTOOLS_SCM_TEST": "generic"} result = read_named_env(name="TEST", dist_name="", env=env) # Should still try dist-specific lookup but fall back to generic assert result == "generic" setuptools-scm-9.2.2/testing/test_pyproject_reading.py000066400000000000000000000153071507525030000233260ustar00rootroot00000000000000from __future__ import annotations from pathlib import Path from unittest.mock import Mock import pytest from setuptools_scm._integration.pyproject_reading import has_build_package_with_extra from setuptools_scm._integration.pyproject_reading import read_pyproject class TestPyProjectReading: """Test the pyproject reading functionality.""" def test_read_pyproject_missing_file_raises(self, tmp_path: Path) -> None: """Test that read_pyproject raises FileNotFoundError when file is missing.""" with pytest.raises(FileNotFoundError): read_pyproject(path=tmp_path / "nonexistent.toml") def test_read_pyproject_existing_file(self, tmp_path: Path) -> None: """Test that read_pyproject reads existing files correctly.""" # Create a simple pyproject.toml pyproject_content = """ [build-system] requires = ["setuptools>=80", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" [project] name = "test-package" dynamic = ["version"] [tool.setuptools_scm] """ pyproject_file = tmp_path / "pyproject.toml" pyproject_file.write_text(pyproject_content, encoding="utf-8") result = read_pyproject(path=pyproject_file) assert result.path == pyproject_file assert result.tool_name == "setuptools_scm" assert result.is_required is True assert result.section_present is True assert result.project_present is True assert result.project.get("name") == "test-package" class TestBuildPackageWithExtra: """Test the has_build_package_with_extra function.""" def test_has_simple_extra(self) -> None: """Test that simple extra is detected correctly.""" requires = ["setuptools-scm[simple]"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is True ) def test_has_no_simple_extra(self) -> None: """Test that missing simple extra is detected correctly.""" requires = ["setuptools-scm"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is False ) def test_has_different_extra(self) -> None: """Test that different extra is not detected as simple.""" requires = ["setuptools-scm[toml]"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is False ) def test_has_multiple_extras_including_simple(self) -> None: """Test that simple extra is detected when multiple extras are present.""" requires = ["setuptools-scm[simple,toml]"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is True ) def test_different_package_with_simple_extra(self) -> None: """Test that simple extra on different package is not detected.""" requires = ["other-package[simple]"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is False ) def test_version_specifier_with_extra(self) -> None: """Test that version specifiers work correctly with extras.""" requires = ["setuptools-scm[simple]>=8.0"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is True ) def test_complex_requirement_with_extra(self) -> None: """Test that complex requirements with extras work correctly.""" requires = ["setuptools-scm[simple]>=8.0,<9.0"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is True ) def test_empty_requires_list(self) -> None: """Test that empty requires list returns False.""" requires: list[str] = [] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is False ) def test_invalid_requirement_string(self) -> None: """Test that invalid requirement strings are handled gracefully.""" requires = ["invalid requirement string"] assert ( has_build_package_with_extra(requires, "setuptools-scm", "simple") is False ) def test_read_pyproject_with_given_definition(monkeypatch: pytest.MonkeyPatch) -> None: """Test that read_pyproject reads existing files correctly.""" monkeypatch.setattr( "setuptools_scm._integration.pyproject_reading.read_toml_content", Mock(side_effect=FileNotFoundError("this test should not read")), ) res = read_pyproject( _given_definition={ "build-system": {"requires": ["setuptools-scm[simple]"]}, "project": {"name": "test-package", "dynamic": ["version"]}, } ) assert res.should_infer() def test_read_pyproject_with_setuptools_dynamic_version_warns() -> None: """Test that warning is issued when version inference is enabled.""" with pytest.warns( UserWarning, match=r"pyproject\.toml: at \[tool\.setuptools\.dynamic\]", ): pyproject_data = read_pyproject( _given_definition={ "build-system": {"requires": ["setuptools-scm[simple]"]}, "project": {"name": "test-package", "dynamic": ["version"]}, "tool": { "setuptools": { "dynamic": {"version": {"attr": "test_package.__version__"}} } }, } ) assert pyproject_data.project_version is None def test_read_pyproject_with_setuptools_dynamic_version_no_warn_when_file_finder_only() -> ( None ): """Test that no warning is issued when only file finder is used (no version inference).""" # When setuptools-scm is used only for file finding (no [tool.setuptools_scm] section, # no [simple] extra, version not in dynamic), it's valid to use tool.setuptools.dynamic.version import warnings with warnings.catch_warnings(record=True) as warning_list: warnings.simplefilter("always") pyproject_data = read_pyproject( _given_definition={ "build-system": {"requires": ["setuptools-scm"]}, "project": {"name": "test-package", "version": "1.0.0"}, "tool": { "setuptools": { "dynamic": {"version": {"attr": "test_package.__version__"}} } }, } ) # Filter to check for the dynamic version warning specifically relevant_warnings = [ w for w in warning_list if "tool.setuptools.dynamic" in str(w.message) ] assert len(relevant_warnings) == 0, ( "Should not warn about tool.setuptools.dynamic when only using file finder" ) assert pyproject_data.project_version == "1.0.0" assert not pyproject_data.should_infer() setuptools-scm-9.2.2/testing/test_regressions.py000066400000000000000000000162221507525030000221560ustar00rootroot00000000000000from __future__ import annotations import pprint import subprocess import sys from dataclasses import replace from importlib.metadata import EntryPoint from importlib.metadata import distribution from pathlib import Path from typing import Sequence import pytest from setuptools_scm import Configuration from setuptools_scm._run_cmd import run from setuptools_scm.git import parse from setuptools_scm.integration import data_from_mime from setuptools_scm.version import meta def test_data_from_mime_ignores_body() -> None: assert data_from_mime( "test", "version: 1.0\r\n\r\nversion: bad", ) == {"version": "1.0"} def test_pkginfo_noscmroot(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """if we are indeed a sdist, the root does not apply""" monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") # we should get the version from pkg-info if git is broken p = tmp_path.joinpath("sub/package") p.mkdir(parents=True) tmp_path.joinpath(".git").mkdir() p.joinpath("setup.py").write_text( """\ from setuptools import setup setup(use_scm_version={"root": ".."}) """, encoding="utf-8", ) res = run([sys.executable, "setup.py", "--version"], p) assert "setuptools-scm was unable to detect version for" in res.stderr assert res.returncode == 1 p.joinpath("PKG-INFO").write_text("Version: 1.0", encoding="utf-8") res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "1.0" try: run("git init", p.parent) except OSError: pass else: res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "0.1.dev0+d20090213" @pytest.mark.issue(164) def test_pip_download(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) subprocess.check_call([sys.executable, "-m", "pip", "download", "lz4==0.9.0"]) def test_use_scm_version_callable( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """use of callable as use_scm_version argument""" monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG") p = tmp_path / "sub" / "package" p.mkdir(parents=True) p.joinpath("setup.py").write_text( """from setuptools import setup def vcfg(): from setuptools_scm.version import guess_next_dev_version def vs(v): return guess_next_dev_version(v) return {"version_scheme": vs} setup(use_scm_version=vcfg) """, encoding="utf-8", ) p.joinpath("PKG-INFO").write_text("Version: 1.0", encoding="utf-8") res = run([sys.executable, "setup.py", "--version"], p) assert res.stdout == "1.0" @pytest.mark.skipif(sys.platform != "win32", reason="this bug is only valid on windows") def test_case_mismatch_on_windows_git(tmp_path: Path) -> None: """Case insensitive path checks on Windows""" camel_case_path = tmp_path / "CapitalizedDir" camel_case_path.mkdir() run("git init", camel_case_path) res = parse(str(camel_case_path).lower(), Configuration()) assert res is not None @pytest.mark.skipif(sys.platform != "win32", reason="this bug is only valid on windows") def test_case_mismatch_nested_dir_windows_git(tmp_path: Path) -> None: """Test case where we have a nested directory with different casing""" # Create git repo in my_repo repo_path = tmp_path / "my_repo" repo_path.mkdir() run("git init", repo_path) # Create a nested directory with specific casing nested_dir = repo_path / "CasedDir" nested_dir.mkdir() # Create a pyproject.toml in the nested directory (nested_dir / "pyproject.toml").write_text( """ [build-system] requires = ["setuptools>=64", "setuptools-scm"] build-backend = "setuptools.build_meta" [project] name = "test-project" dynamic = ["version"] [tool.setuptools_scm] """, encoding="utf-8", ) # Add and commit the file run("git add .", repo_path) run("git commit -m 'Initial commit'", repo_path) # Now try to parse from the nested directory with lowercase path # This simulates: cd my_repo/caseddir (lowercase) when actual dir is CasedDir lowercase_nested_path = str(nested_dir).replace("CasedDir", "caseddir") # This should trigger the assertion error in _git_toplevel try: res = parse(lowercase_nested_path, Configuration()) # If we get here without assertion error, the bug is already fixed or not triggered print(f"Parse succeeded with result: {res}") except AssertionError as e: print(f"AssertionError caught as expected: {e}") # Re-raise so the test fails, showing we reproduced the bug raise def test_case_mismatch_force_assertion_failure(tmp_path: Path) -> None: """Force the assertion failure by directly calling _git_toplevel with mismatched paths""" from setuptools_scm._file_finders.git import _git_toplevel # Create git repo structure repo_path = tmp_path / "my_repo" repo_path.mkdir() run("git init", repo_path) # Create nested directory nested_dir = repo_path / "CasedDir" nested_dir.mkdir() # Add and commit something to make it a valid repo (nested_dir / "test.txt").write_text("test", encoding="utf-8") run("git add .", repo_path) run("git commit -m 'Initial commit'", repo_path) # Now call _git_toplevel with a path that has different casing # This should cause the assertion to fail lowercase_nested_path = str(nested_dir).replace("CasedDir", "caseddir") try: result = _git_toplevel(lowercase_nested_path) print(f"_git_toplevel returned: {result}") # If no assertion error, either the bug is fixed or we didn't trigger it properly except AssertionError as e: print(f"AssertionError as expected: {e}") raise # Let the test fail to show we reproduced the issue def test_entrypoints_load() -> None: d = distribution("setuptools-scm") eps = d.entry_points failed: list[tuple[EntryPoint, Exception]] = [] for ep in eps: try: ep.load() except Exception as e: failed.append((ep, e)) if failed: pytest.fail(pprint.pformat(failed)) def test_write_to_absolute_path_passes_when_subdir_of_root(tmp_path: Path) -> None: c = Configuration(root=tmp_path, write_to=tmp_path / "VERSION.py") v = meta("1.0", config=c) from setuptools_scm._get_version_impl import write_version_files with pytest.warns(DeprecationWarning, match=".*write_to=.* is a absolute.*"): write_version_files(c, "1.0", v) write_version_files(replace(c, write_to="VERSION.py"), "1.0", v) subdir = tmp_path / "subdir" subdir.mkdir() with pytest.raises( # todo: python version specific error list ValueError, match=r".*VERSION.py' .* .*subdir.*", ): write_version_files(replace(c, root=subdir), "1.0", v) @pytest.mark.parametrize( ("input", "expected"), [ ("1.0", (1, 0)), ("1.0a2", (1, 0, "a2")), ("1.0.b2dev1", (1, 0, "b2", "dev1")), ("1.0.dev1", (1, 0, "dev1")), ], ) def test_version_as_tuple(input: str, expected: Sequence[int | str]) -> None: from setuptools_scm._version_cls import _version_as_tuple assert _version_as_tuple(input) == expected setuptools-scm-9.2.2/testing/test_version.py000066400000000000000000000400121507525030000212720ustar00rootroot00000000000000from __future__ import annotations import re from dataclasses import replace from datetime import date from datetime import datetime from datetime import timedelta from datetime import timezone from typing import Any import pytest from setuptools_scm import Configuration from setuptools_scm import NonNormalizedVersion from setuptools_scm.version import ScmVersion from setuptools_scm.version import calver_by_date from setuptools_scm.version import format_version from setuptools_scm.version import guess_next_date_ver from setuptools_scm.version import guess_next_version from setuptools_scm.version import meta from setuptools_scm.version import no_guess_dev_version from setuptools_scm.version import only_version from setuptools_scm.version import release_branch_semver_version from setuptools_scm.version import simplified_semver_version c = Configuration() c_non_normalize = Configuration(version_cls=NonNormalizedVersion) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param(meta("1.0", config=c), "1.0.0", id="short_tag"), pytest.param( meta("1.0.0", distance=2, branch="default", config=c), "1.0.1.dev2", id="normal_branch", ), pytest.param( meta("1.0", distance=2, branch="default", config=c), "1.0.1.dev2", id="normal_branch_short_tag", ), pytest.param( meta("1.0.0", distance=2, branch="feature", config=c), "1.1.0.dev2", id="feature_branch", ), pytest.param( meta("1.0", distance=2, branch="feature", config=c), "1.1.0.dev2", id="feature_branch_short_tag", ), pytest.param( meta("1.0.0", distance=2, branch="features/test", config=c), "1.1.0.dev2", id="feature_in_branch", ), pytest.param( meta(NonNormalizedVersion("v1.0"), distance=2, branch="default", config=c), "1.0.1.dev2", id="non-normalized-allowed", ), ], ) def test_next_semver(version: ScmVersion, expected_next: str) -> None: computed = simplified_semver_version(version) assert computed == expected_next def test_next_semver_bad_tag() -> None: # Create a mock version class that represents an invalid version for testing error handling from typing import cast from setuptools_scm._version_cls import _VersionT class BrokenVersionForTest: """A mock version that behaves like a string but passes type checking.""" def __init__(self, version_str: str): self._version_str = version_str def __str__(self) -> str: return self._version_str def __repr__(self) -> str: return f"BrokenVersionForTest({self._version_str!r})" # Cast to the expected type to avoid type checking issues broken_tag = cast(_VersionT, BrokenVersionForTest("1.0.0-foo")) version = meta(broken_tag, preformatted=True, config=c) with pytest.raises( ValueError, match=r"1\.0\.0-foo.* can't be parsed as numeric version" ): simplified_semver_version(version) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param( meta("1.0.0", distance=2, branch="master", config=c), "1.1.0.dev2", id="development_branch", ), pytest.param( meta("1.0.0rc1", distance=2, branch="master", config=c), "1.1.0.dev2", id="development_branch_release_candidate", ), pytest.param( meta("1.0.0", distance=2, branch="maintenance/1.0.x", config=c), "1.0.1.dev2", id="release_branch_legacy_version", ), pytest.param( meta("1.0.0", distance=2, branch="v1.0.x", config=c), "1.0.1.dev2", id="release_branch_with_v_prefix", ), pytest.param( meta("1.0.0", distance=2, branch="release-1.0", config=c), "1.0.1.dev2", id="release_branch_with_prefix", ), pytest.param( meta("1.0.0", distance=2, branch="bugfix/3434", config=c), "1.1.0.dev2", id="false_positive_release_branch", ), ], ) def test_next_release_branch_semver(version: ScmVersion, expected_next: str) -> None: computed = release_branch_semver_version(version) assert computed == expected_next def m(tag: str, **kw: Any) -> ScmVersion: return meta(tag, **kw, config=c) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param( m("1.0.0", distance=2), "1.0.0.post1.dev2", id="dev_distance", ), pytest.param( m("1.0.dev0", distance=2), "1.0.dev2", id="dev_distance_after_dev_tag" ), pytest.param( m("1.0", distance=2), "1.0.post1.dev2", id="dev_distance_short_tag", ), pytest.param( m("1.0.0"), "1.0.0", id="no_dev_distance", ), ], ) def test_no_guess_version(version: ScmVersion, expected_next: str) -> None: computed = no_guess_dev_version(version) assert computed == expected_next @pytest.mark.parametrize( ("version", "match"), [ ("1.0.dev1", "choosing custom numbers for the `.devX` distance"), ("1.0.post1", "already is a post release"), ], ) def test_no_guess_version_bad(version: str, match: str) -> None: with pytest.raises(ValueError, match=match): no_guess_dev_version(m(version, distance=1)) def test_bump_dev_version_zero() -> None: assert guess_next_version(m("1.0.dev0")) == "1.0" def test_bump_dev_version_nonzero_raises() -> None: match = ( "choosing custom numbers for the `.devX` distance " "is not supported.\n " "The 1.0.dev1 can't be bumped\n" "Please drop the tag or create a new supported one ending in .dev0" ) with pytest.raises(ValueError, match=match): guess_next_version(m("1.0.dev1")) @pytest.mark.parametrize( "version", [ "1.dev0", "1.0.dev456", "1.0a1", "1.0a2.dev456", "1.0a12.dev456", "1.0a12", "1.0b1.dev456", "1.0b2", "1.0b2.post345.dev456", "1.0b2.post345", "1.0rc1.dev456", "1.0rc1", "1.0", "1.0.post456.dev34", "1.0.post456", "1.0.15", "1.1.dev1", ], ) def test_only_version(version: str) -> None: assert version == only_version(meta(version, config=c)) assert version == only_version(meta(version, distance=2, config=c)) @pytest.mark.parametrize( ("tag", "expected"), [ ("v1.0.0", "1.0.0"), ("v1.0.0-rc.1", "1.0.0rc1"), ("v1.0.0-rc.1+-25259o4382757gjurh54", "1.0.0rc1"), ], ) def test_tag_regex1(tag: str, expected: str) -> None: if "+" in tag: # pytest bug wrt cardinality with pytest.warns(UserWarning): # noqa: PT030 result = meta(tag, config=c) else: result = meta(tag, config=c) assert not isinstance(result.tag, str) assert result.tag.public == expected def test_regex_match_but_no_version() -> None: with pytest.raises( ValueError, match=( r'The tag_regex "\(\?P\)\.\*" matched tag "v1",' " however the matched group has no value" ), ): meta("v1", config=replace(c, tag_regex=re.compile(r"(?P).*"))) @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/471") def test_version_bump_bad() -> None: class YikesVersion: val: str def __init__(self, val: str) -> None: self.val = val def __str__(self) -> str: return self.val config = Configuration(version_cls=YikesVersion) # type: ignore[arg-type] with pytest.raises( ValueError, match=r".*does not end with a number to bump, " "please correct or use a custom version scheme", ): guess_next_version(tag_version=meta("2.0.0-alpha.5-PMC", config=config)) def test_format_version_schemes() -> None: version = meta( "1.0", config=replace( c, local_scheme="no-local-version", version_scheme=[ # type: ignore[arg-type] lambda v: None, "guess-next-dev", ], ), ) assert format_version(version) == "1.0" def test_custom_version_schemes() -> None: version = meta( "1.0", config=replace( c, local_scheme="no-local-version", version_scheme="setuptools_scm.version:no_guess_dev_version", ), ) custom_computed = format_version(version) assert custom_computed == no_guess_dev_version(version) # Fixed time for consistent test behavior across timezone boundaries # This prevents issue #687 where tests failed around midnight in non-UTC timezones _TEST_TIME = datetime(2023, 12, 15, 12, 0, 0, tzinfo=timezone.utc) def date_offset(base_date: date | None = None, days_offset: int = 0) -> date: if base_date is None: base_date = _TEST_TIME.date() return base_date - timedelta(days=days_offset) def date_to_str( base_date: date | None = None, days_offset: int = 0, fmt: str = "%y.%m.%d", ) -> str: return format(date_offset(base_date, days_offset), fmt) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param( meta(date_to_str(days_offset=3), config=c_non_normalize), date_to_str(days_offset=3), id="exact", ), pytest.param( meta(date_to_str() + ".1", config=c_non_normalize), date_to_str() + ".1", id="exact patch", ), pytest.param( meta("20.01.02", config=c), "20.1.2", id="leading 0s", ), pytest.param( meta( date_to_str(days_offset=3), config=c_non_normalize, dirty=True, time=_TEST_TIME, ), date_to_str() + ".0.dev0", id="dirty other day", ), pytest.param( meta( date_to_str(), config=c_non_normalize, distance=2, branch="default", time=_TEST_TIME, ), date_to_str() + ".1.dev2", id="normal branch", ), pytest.param( meta(date_to_str(fmt="%Y.%m.%d"), config=c_non_normalize), date_to_str(fmt="%Y.%m.%d"), id="4 digits year", ), pytest.param( meta( date_to_str(), config=c_non_normalize, distance=2, branch="release-2021.05.06", ), "2021.05.06", id="release branch", ), pytest.param( meta( date_to_str() + ".2", config=c_non_normalize, distance=2, branch="release-21.5.1", ), "21.5.1", id="release branch short", ), pytest.param( meta( date_to_str(days_offset=3) + ".2", config=c_non_normalize, node_date=date_offset(days_offset=2), ), date_to_str(days_offset=3) + ".2", id="node date clean", ), pytest.param( meta( date_to_str(days_offset=2) + ".2", config=c_non_normalize, distance=2, node_date=date_offset(days_offset=2), ), date_to_str(days_offset=2) + ".3.dev2", id="node date distance", ), pytest.param( meta( "1.2.0", config=c_non_normalize, distance=2, node_date=date_offset(days_offset=2), ), date_to_str(days_offset=2) + ".0.dev2", marks=pytest.mark.filterwarnings( "ignore:.*not correspond to a valid versioning date.*:UserWarning" ), id="using on old version tag", ), ], ) def test_calver_by_date(version: ScmVersion, expected_next: str) -> None: computed = calver_by_date(version) assert computed == expected_next @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="SemVer exact stays"), pytest.param( meta("1.0.0", config=c_non_normalize, dirty=True, time=_TEST_TIME), "23.12.15.0.dev0", id="SemVer dirty is replaced by date", marks=pytest.mark.filterwarnings("ignore:.*legacy version.*:UserWarning"), ), ], ) def test_calver_by_date_semver(version: ScmVersion, expected_next: str) -> None: computed = calver_by_date(version) assert computed == expected_next def test_calver_by_date_future_warning() -> None: with pytest.warns(UserWarning, match="your previous tag*"): calver_by_date( meta( date_to_str(days_offset=-2), config=c_non_normalize, distance=2, time=_TEST_TIME, ) ) @pytest.mark.parametrize( ("tag", "node_date", "expected"), [ pytest.param("20.03.03", date(2020, 3, 4), "20.03.04.0", id="next day"), pytest.param("20.03.03", date(2020, 3, 3), "20.03.03.1", id="same day"), pytest.param( "20.03.03.2", date(2020, 3, 3), "20.03.03.3", id="same day with patch" ), pytest.param( "v20.03.03", date(2020, 3, 4), "v20.03.04.0", id="next day with v prefix" ), ], ) def test_calver_guess_next_data(tag: str, node_date: date, expected: str) -> None: version = meta(tag, config=c_non_normalize, node_date=node_date) next = guess_next_date_ver( version, node_date=node_date, version_cls=c_non_normalize.version_cls, ) assert next == expected def test_custom_version_cls() -> None: """Test that we can pass our own version class instead of pkg_resources""" class MyVersion: def __init__(self, tag_str: str) -> None: self.tag = tag_str def __str__(self) -> str: return f"Custom {self.tag}" def __repr__(self) -> str: return f"MyVersion" @property def public(self) -> str: """The public portion of the version (without local part).""" return self.tag.split("+")[0] @property def local(self) -> str | None: """The local version segment.""" if "+" in self.tag: return self.tag.split("+", 1)[1] return None config = Configuration(version_cls=MyVersion) # type: ignore[arg-type] scm_version = meta("1.0.0-foo", config=config) assert isinstance(scm_version.tag, MyVersion) assert str(scm_version.tag) == "Custom 1.0.0-foo" @pytest.mark.parametrize("config_key", ["version_scheme", "local_scheme"]) def test_no_matching_entrypoints(config_key: str) -> None: version = meta( "1.0", config=replace(c, **{config_key: "nonexistent"}), # type: ignore[arg-type] ) with pytest.raises( ValueError, match=( r'Couldn\'t find any implementations for entrypoint "setuptools_scm\..*?"' ' with value "nonexistent"' ), ): format_version(version) def test_all_entrypoints_return_none() -> None: version = meta( "1.0", config=replace( c, version_scheme=lambda v: None, # type: ignore[arg-type,return-value] ), ) with pytest.raises( ValueError, match=( 'None of the "setuptools_scm.version_scheme" entrypoints matching' r" .*? returned a value." ), ): format_version(version) setuptools-scm-9.2.2/testing/test_version_inference.py000066400000000000000000000205041507525030000233140ustar00rootroot00000000000000from __future__ import annotations from types import SimpleNamespace from typing import Any import pytest from setuptools_scm._integration.pyproject_reading import PyProjectData from setuptools_scm._integration.version_inference import VersionInferenceConfig from setuptools_scm._integration.version_inference import VersionInferenceNoOp from setuptools_scm._integration.version_inference import VersionInferenceResult from setuptools_scm._integration.version_inference import VersionInferenceWarning from setuptools_scm._integration.version_inference import get_version_inference_config # Common test data PYPROJECT = SimpleNamespace( DEFAULT=PyProjectData.for_testing( is_required=True, section_present=True, project_present=True ), WITHOUT_TOOL_SECTION=PyProjectData.for_testing( is_required=True, section_present=False, project_present=True ), ONLY_REQUIRED=PyProjectData.for_testing( is_required=True, section_present=False, project_present=False ), WITHOUT_PROJECT=PyProjectData.for_testing( is_required=True, section_present=True, project_present=False ), ) OVERRIDES = SimpleNamespace( NOT_GIVEN=None, EMPTY={}, CALVER={"version_scheme": "calver"}, UNRELATED={"key": "value"}, ) WARNING_PACKAGE = VersionInferenceWarning( message="version of test_package already set", ) WARNING_NO_PACKAGE = VersionInferenceWarning( message="version of None already set", ) NOOP = VersionInferenceNoOp() def expect_config( *, dist_name: str | None = "test_package", current_version: str | None, pyproject_data: PyProjectData = PYPROJECT.DEFAULT, overrides: dict[str, Any] | None = None, expected: type[VersionInferenceConfig] | VersionInferenceWarning | VersionInferenceNoOp, ) -> None: """Helper to test get_version_inference_config and assert expected result type.""" __tracebackhide__ = True result = get_version_inference_config( dist_name=dist_name, current_version=current_version, pyproject_data=pyproject_data, overrides=overrides, ) expectation: VersionInferenceResult if expected == VersionInferenceConfig: expectation = VersionInferenceConfig( dist_name=dist_name, pyproject_data=pyproject_data, overrides=overrides, ) else: assert isinstance(expected, (VersionInferenceNoOp, VersionInferenceWarning)) expectation = expected assert result == expectation infer_implied = pytest.mark.parametrize( ("overrides", "pyproject_data"), [ pytest.param( OVERRIDES.EMPTY, PYPROJECT.DEFAULT, id="empty_overrides_default_pyproject" ), pytest.param( OVERRIDES.EMPTY, PYPROJECT.WITHOUT_TOOL_SECTION, id="empty_overrides_without_tool_section", ), pytest.param( OVERRIDES.NOT_GIVEN, PYPROJECT.DEFAULT, id="infer_version_default_pyproject", ), ], ) @pytest.mark.parametrize("package_name", ["test_package", None]) @infer_implied def test_implied_with_version_warns( package_name: str | None, overrides: dict[str, Any] | None, pyproject_data: PyProjectData, ) -> None: expect_config( dist_name=package_name, current_version="1.0.0", pyproject_data=pyproject_data, overrides=overrides, expected=WARNING_PACKAGE if package_name else WARNING_NO_PACKAGE, ) @pytest.mark.parametrize("package_name", ["test_package", None]) @infer_implied def test_implied_without_version_infers( package_name: str | None, overrides: dict[str, Any] | None, pyproject_data: PyProjectData, ) -> None: expect_config( dist_name=package_name, current_version=None, pyproject_data=pyproject_data, overrides=overrides, expected=VersionInferenceConfig, ) def test_no_config_no_infer() -> None: expect_config( current_version=None, pyproject_data=PYPROJECT.WITHOUT_TOOL_SECTION, overrides=OVERRIDES.NOT_GIVEN, expected=NOOP, ) class TestVersionInferenceDecision: """Test the version inference decision logic.""" def test_setuptools_scm_required_no_project_section_infer_version(self) -> None: """We don't infer without tool section even if required: infer_version path.""" expect_config( current_version=None, pyproject_data=PYPROJECT.ONLY_REQUIRED, overrides=None, expected=NOOP, ) def test_setuptools_scm_required_no_project_section_version_keyword(self) -> None: """Test that we DO infer when setuptools-scm is required but no project section and use_scm_version=True.""" expect_config( current_version=None, pyproject_data=PYPROJECT.ONLY_REQUIRED, overrides=OVERRIDES.EMPTY, expected=VersionInferenceConfig, ) def test_setuptools_scm_required_no_project_section_version_keyword_with_config( self, ) -> None: """Test that we DO infer when setuptools-scm is required but no project section and use_scm_version={config}.""" expect_config( current_version=None, pyproject_data=PYPROJECT.ONLY_REQUIRED, overrides=OVERRIDES.CALVER, expected=VersionInferenceConfig, ) def test_tool_section_present(self) -> None: """We infer when tool section is present.""" expect_config( current_version=None, pyproject_data=PYPROJECT.WITHOUT_PROJECT, expected=VersionInferenceConfig, ) def test_simple_extra_with_dynamic_version_infers(self) -> None: """We infer when setuptools-scm[simple] is in build-system.requires and version is dynamic.""" pyproject_data = PyProjectData.for_testing( is_required=True, section_present=False, project_present=True, has_dynamic_version=True, build_requires=["setuptools-scm[simple]"], ) expect_config( current_version=None, pyproject_data=pyproject_data, expected=VersionInferenceConfig, ) def test_simple_extra_without_dynamic_version_no_infer(self) -> None: """We don't infer when setuptools-scm[simple] is present but version is not dynamic.""" pyproject_data = PyProjectData.for_testing( is_required=True, section_present=False, project_present=True, has_dynamic_version=False, build_requires=["setuptools-scm[simple]"], ) expect_config( current_version=None, pyproject_data=pyproject_data, expected=NOOP, ) def test_no_simple_extra_with_dynamic_version_no_infer(self) -> None: """We don't infer when setuptools-scm (without simple extra) is present even with dynamic version.""" pyproject_data = PyProjectData.for_testing( is_required=True, section_present=False, project_present=True, has_dynamic_version=True, build_requires=["setuptools-scm"], ) expect_config( current_version=None, pyproject_data=pyproject_data, expected=NOOP, ) def test_simple_extra_no_project_section_no_infer(self) -> None: """We don't infer when setuptools-scm[simple] is present but no project section.""" pyproject_data = PyProjectData.for_testing( is_required=True, section_present=False, project_present=False, build_requires=["setuptools-scm[simple]"], ) expect_config( current_version=None, pyproject_data=pyproject_data, expected=NOOP, ) def test_simple_extra_with_version_warns(self) -> None: """We warn when setuptools-scm[simple] is present with dynamic version but version is already set.""" pyproject_data = PyProjectData.for_testing( is_required=True, section_present=False, project_present=True, has_dynamic_version=True, build_requires=["setuptools-scm[simple]"], ) expect_config( current_version="1.0.0", pyproject_data=pyproject_data, expected=WARNING_PACKAGE, ) setuptools-scm-9.2.2/testing/wd_wrapper.py000066400000000000000000000042011507525030000207200ustar00rootroot00000000000000from __future__ import annotations import itertools from pathlib import Path from typing import Any class WorkDir: """a simple model for a""" commit_command: str signed_commit_command: str add_command: str def __repr__(self) -> str: return f"" def __init__(self, cwd: Path) -> None: self.cwd = cwd self.__counter = itertools.count() def __call__(self, cmd: list[str] | str, *, timeout: int = 10, **kw: object) -> str: if kw: assert isinstance(cmd, str), "formatting the command requires text input" cmd = cmd.format(**kw) from setuptools_scm._run_cmd import run return run(cmd, cwd=self.cwd, timeout=timeout).stdout def write(self, name: str, content: str | bytes) -> Path: path = self.cwd / name if isinstance(content, bytes): path.write_bytes(content) else: path.write_text(content, encoding="utf-8") return path def _reason(self, given_reason: str | None) -> str: if given_reason is None: return f"number-{next(self.__counter)}" else: return given_reason def add_and_commit( self, reason: str | None = None, signed: bool = False, **kwargs: object ) -> None: self(self.add_command) self.commit(reason=reason, signed=signed, **kwargs) def commit(self, reason: str | None = None, signed: bool = False) -> None: reason = self._reason(reason) self( self.commit_command if not signed else self.signed_commit_command, reason=reason, ) def commit_testfile(self, reason: str | None = None, signed: bool = False) -> None: reason = self._reason(reason) self.write("test.txt", f"test {reason}") self(self.add_command) self.commit(reason=reason, signed=signed) def get_version(self, **kw: Any) -> str: __tracebackhide__ = True from setuptools_scm import get_version version = get_version(root=self.cwd, fallback_root=self.cwd, **kw) print(self.cwd.name, version, sep=": ") return version setuptools-scm-9.2.2/tox.ini000066400000000000000000000012451507525030000160370ustar00rootroot00000000000000[tox] envlist=py{38,39,310,311,312,313},check_readme,check-dist requires= tox>4 [flake8] max-complexity = 10 max-line-length = 88 ignore=E203,W503 [testenv] usedevelop=True dependency_groups = test deps = pytest pytest-cov pytest-timeout pytest-xdist commands= python -X warn_default_encoding -m pytest {posargs} [testenv:check_readme] skip_install=True deps= check-manifest docutils pygments typing_extensions hatchling rich commands= check-manifest --no-build-isolation [testenv:check_dist] skip_install = true deps= build twine commands= python -m build twine check dist/* #XXX: envs for hg versions setuptools-scm-9.2.2/uv.lock000066400000000000000000007735701507525030000160510ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.8" resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "argh" version = "0.30.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/56/12/353fc6b87bfaf98fe5a0c95e8cba03acd24ab0eea99d1307e1ab7a3809ba/argh-0.30.5.tar.gz", hash = "sha256:b37dfd617a09d19a4a7bcaed0e060b288bc7ac8dfdc0facf886a49a25ff33728", size = 63041, upload-time = "2023-12-25T22:05:32.15Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/51/99d9dfcb588e15b4d9630f98f84d4e766d03b47da52839395057dbbe2df4/argh-0.30.5-py3-none-any.whl", hash = "sha256:3844e955d160f0689a3cdca06a59dfcfbf1fcea70029d67d473f73503341e0d8", size = 44635, upload-time = "2023-12-25T22:05:29.35Z" }, ] [[package]] name = "astunparse" version = "1.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six", marker = "python_full_version < '3.9'" }, { name = "wheel", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290, upload-time = "2019-12-22T18:12:13.129Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732, upload-time = "2019-12-22T18:12:11.297Z" }, ] [[package]] name = "babel" version = "2.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytz", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] name = "backrefs" version = "5.7.post1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/df/30/903f35159c87ff1d92aa3fcf8cb52de97632a21e0ae43ed940f5d033e01a/backrefs-5.7.post1.tar.gz", hash = "sha256:8b0f83b770332ee2f1c8244f4e03c77d127a0fa529328e6a0e77fa25bee99678", size = 6582270, upload-time = "2024-06-16T18:38:20.166Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/24/bb/47fc255d1060dcfd55b460236380edd8ebfc5b2a42a0799ca90c9fc983e3/backrefs-5.7.post1-py310-none-any.whl", hash = "sha256:c5e3fd8fd185607a7cb1fefe878cfb09c34c0be3c18328f12c574245f1c0287e", size = 380429, upload-time = "2024-06-16T18:38:10.131Z" }, { url = "https://files.pythonhosted.org/packages/89/72/39ef491caef3abae945f5a5fd72830d3b596bfac0630508629283585e213/backrefs-5.7.post1-py311-none-any.whl", hash = "sha256:712ea7e494c5bf3291156e28954dd96d04dc44681d0e5c030adf2623d5606d51", size = 392234, upload-time = "2024-06-16T18:38:12.283Z" }, { url = "https://files.pythonhosted.org/packages/6a/00/33403f581b732ca70fdebab558e8bbb426a29c34e0c3ed674a479b74beea/backrefs-5.7.post1-py312-none-any.whl", hash = "sha256:a6142201c8293e75bce7577ac29e1a9438c12e730d73a59efdd1b75528d1a6c5", size = 398110, upload-time = "2024-06-16T18:38:14.257Z" }, { url = "https://files.pythonhosted.org/packages/5d/ea/df0ac74a26838f6588aa012d5d801831448b87d0a7d0aefbbfabbe894870/backrefs-5.7.post1-py38-none-any.whl", hash = "sha256:ec61b1ee0a4bfa24267f6b67d0f8c5ffdc8e0d7dc2f18a2685fd1d8d9187054a", size = 369477, upload-time = "2024-06-16T18:38:16.196Z" }, { url = "https://files.pythonhosted.org/packages/6f/e8/e43f535c0a17a695e5768670fc855a0e5d52dc0d4135b3915bfa355f65ac/backrefs-5.7.post1-py39-none-any.whl", hash = "sha256:05c04af2bf752bb9a6c9dcebb2aff2fab372d3d9d311f2a138540e307756bd3a", size = 380429, upload-time = "2024-06-16T18:38:18.079Z" }, ] [[package]] name = "backrefs" version = "5.9" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" }, { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" }, { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" }, { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" }, { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9", size = 411762, upload-time = "2025-06-22T19:34:11.037Z" }, { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, ] [[package]] name = "bracex" version = "2.5.post1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/d6/6c/57418c4404cd22fe6275b8301ca2b46a8cdaa8157938017a9ae0b3edf363/bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6", size = 26641, upload-time = "2024-09-28T21:41:22.017Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4b/02/8db98cdc1a58e0abd6716d5e63244658e6e63513c65f469f34b6f1053fd0/bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6", size = 11558, upload-time = "2024-09-28T21:41:21.016Z" }, ] [[package]] name = "bracex" version = "2.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, ] [[package]] name = "brei" version = "0.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argh", marker = "python_full_version >= '3.11'" }, { name = "rich", marker = "python_full_version >= '3.11'" }, { name = "rich-argparse", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/97/503b9bc095501678cd6e10a940e042d36f2a77ee41b798e93477d142543d/brei-0.2.4.tar.gz", hash = "sha256:fb8a1f191a1c70a81a9df366c530e01938b8082e7198e898d4ea8eb53066fa92", size = 18853, upload-time = "2024-11-25T10:17:11.418Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/86/6d/e5c216146c0654cbf7d4e0567a075e3f13f2700bc8812b9ec3a35bf1099b/brei-0.2.4-py3-none-any.whl", hash = "sha256:46967640f1aebe3c698456434a8ff0b2901860c4544778ab763803875e925155", size = 22436, upload-time = "2024-11-25T10:17:10.292Z" }, ] [[package]] name = "build" version = "1.2.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "os_name == 'nt'" }, { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.10.2'" }, { name = "packaging" }, { name = "pyproject-hooks" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, ] [[package]] name = "certifi" version = "2025.7.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, { url = "https://files.pythonhosted.org/packages/4c/fd/f700cfd4ad876def96d2c769d8a32d808b12d1010b6003dc6639157f99ee/charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", size = 198257, upload-time = "2025-05-02T08:33:45.511Z" }, { url = "https://files.pythonhosted.org/packages/3a/95/6eec4cbbbd119e6a402e3bfd16246785cc52ce64cf21af2ecdf7b3a08e91/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", size = 143453, upload-time = "2025-05-02T08:33:47.463Z" }, { url = "https://files.pythonhosted.org/packages/b6/b3/d4f913660383b3d93dbe6f687a312ea9f7e89879ae883c4e8942048174d4/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", size = 153130, upload-time = "2025-05-02T08:33:50.568Z" }, { url = "https://files.pythonhosted.org/packages/e5/69/7540141529eabc55bf19cc05cd9b61c2078bebfcdbd3e799af99b777fc28/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", size = 145688, upload-time = "2025-05-02T08:33:52.828Z" }, { url = "https://files.pythonhosted.org/packages/2e/bb/d76d3d6e340fb0967c43c564101e28a78c9a363ea62f736a68af59ee3683/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", size = 147418, upload-time = "2025-05-02T08:33:54.718Z" }, { url = "https://files.pythonhosted.org/packages/3e/ef/b7c1f39c0dc3808160c8b72e0209c2479393966313bfebc833533cfff9cc/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", size = 150066, upload-time = "2025-05-02T08:33:56.597Z" }, { url = "https://files.pythonhosted.org/packages/20/26/4e47cc23d2a4a5eb6ed7d6f0f8cda87d753e2f8abc936d5cf5ad2aae8518/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", size = 144499, upload-time = "2025-05-02T08:33:58.637Z" }, { url = "https://files.pythonhosted.org/packages/d7/9c/efdf59dd46593cecad0548d36a702683a0bdc056793398a9cd1e1546ad21/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", size = 152954, upload-time = "2025-05-02T08:34:00.552Z" }, { url = "https://files.pythonhosted.org/packages/59/b3/4e8b73f7299d9aaabd7cd26db4a765f741b8e57df97b034bb8de15609002/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", size = 155876, upload-time = "2025-05-02T08:34:02.527Z" }, { url = "https://files.pythonhosted.org/packages/53/cb/6fa0ccf941a069adce3edb8a1e430bc80e4929f4d43b5140fdf8628bdf7d/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", size = 153186, upload-time = "2025-05-02T08:34:04.481Z" }, { url = "https://files.pythonhosted.org/packages/ac/c6/80b93fabc626b75b1665ffe405e28c3cef0aae9237c5c05f15955af4edd8/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", size = 148007, upload-time = "2025-05-02T08:34:06.888Z" }, { url = "https://files.pythonhosted.org/packages/41/eb/c7367ac326a2628e4f05b5c737c86fe4a8eb3ecc597a4243fc65720b3eeb/charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", size = 97923, upload-time = "2025-05-02T08:34:08.792Z" }, { url = "https://files.pythonhosted.org/packages/7c/02/1c82646582ccf2c757fa6af69b1a3ea88744b8d2b4ab93b7686b2533e023/charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", size = 105020, upload-time = "2025-05-02T08:34:10.6Z" }, { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" }, { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" }, { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" }, { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" }, { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" }, { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" }, { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" }, { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" }, { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" }, { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" }, { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" }, { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" }, { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" }, { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, ] [[package]] name = "click" version = "8.2.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "copier" version = "9.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "python_full_version >= '3.11'" }, { name = "dunamai", marker = "python_full_version >= '3.11'" }, { name = "funcy", marker = "python_full_version >= '3.11'" }, { name = "jinja2", marker = "python_full_version >= '3.11'" }, { name = "jinja2-ansible-filters", marker = "python_full_version >= '3.11'" }, { name = "packaging", marker = "python_full_version >= '3.11'" }, { name = "pathspec", marker = "python_full_version >= '3.11'" }, { name = "platformdirs", version = "4.3.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "plumbum", marker = "python_full_version >= '3.11'" }, { name = "pydantic", marker = "python_full_version >= '3.11'" }, { name = "pygments", marker = "python_full_version >= '3.11'" }, { name = "pyyaml", marker = "python_full_version >= '3.11'" }, { name = "questionary", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/78/1246b4a1ddeb404037f76efedab9b42ada4e6ba56c574d8846e2a6b8f498/copier-9.8.0.tar.gz", hash = "sha256:343ac1eb65e678aa355690d7f19869ef07cabf837f511a87ed452443c085ec58", size = 579353, upload-time = "2025-07-07T18:47:03.803Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/ed/839c91ff365f24756c90189e07f9de226d2e37cbc03c635f5d16d45d79cb/copier-9.8.0-py3-none-any.whl", hash = "sha256:ca0bee47f198b66cec926c4f1a3aa77f11ee0102624369c10e42ca9058c0a891", size = 55744, upload-time = "2025-07-07T18:47:01.905Z" }, ] [[package]] name = "dunamai" version = "1.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/2f/194d9a34c4d831c6563d2d990720850f0baef9ab60cb4ad8ae0eff6acd34/dunamai-1.25.0.tar.gz", hash = "sha256:a7f8360ea286d3dbaf0b6a1473f9253280ac93d619836ad4514facb70c0719d1", size = 46155, upload-time = "2025-07-04T19:25:56.082Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/36/41/04e2a649058b0713b00d6c9bd22da35618bb157289e05d068e51fddf8d7e/dunamai-1.25.0-py3-none-any.whl", hash = "sha256:7f9dc687dd3256e613b6cc978d9daabfd2bb5deb8adc541fc135ee423ffa98ab", size = 27022, upload-time = "2025-07-04T19:25:54.863Z" }, ] [[package]] name = "entangled-cli" version = "2.1.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argh", marker = "python_full_version >= '3.11'" }, { name = "brei", marker = "python_full_version >= '3.11'" }, { name = "copier", marker = "python_full_version >= '3.11'" }, { name = "filelock", marker = "python_full_version >= '3.11'" }, { name = "mawk", marker = "python_full_version >= '3.11'" }, { name = "pexpect", marker = "python_full_version >= '3.11'" }, { name = "pyyaml", marker = "python_full_version >= '3.11'" }, { name = "rich", marker = "python_full_version >= '3.11'" }, { name = "rich-argparse", marker = "python_full_version >= '3.11'" }, { name = "tomlkit", marker = "python_full_version >= '3.11'" }, { name = "watchdog", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6a/35/568227a7e821e42520f769ca9bee162dfd8ef03e18616d1201577a2e25e0/entangled_cli-2.1.13.tar.gz", hash = "sha256:c17b1e479f1ac9689187a4ed7eb31c066af3458cdc663afffcb0f10051228642", size = 36900, upload-time = "2025-04-24T23:01:11.703Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/07/8c/cf9c69a44aa28900ceef8ab17aafc2beefab13d3cab6b771a4e3109e3c2e/entangled_cli-2.1.13-py3-none-any.whl", hash = "sha256:01822e05e393934ec1bc9f867d179581703282352b836b69d5cf654ee8e6e6b7", size = 50175, upload-time = "2025-04-24T23:01:09.981Z" }, ] [[package]] name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] [[package]] name = "filelock" version = "3.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, ] [[package]] name = "flake8" version = "5.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.8.1'", ] dependencies = [ { name = "mccabe", marker = "python_full_version < '3.8.1'" }, { name = "pycodestyle", version = "2.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8.1'" }, { name = "pyflakes", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8.1'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ad/00/9808c62b2d529cefc69ce4e4a1ea42c0f855effa55817b7327ec5b75e60a/flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", size = 145862, upload-time = "2022-08-03T23:21:27.108Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cf/a0/b881b63a17a59d9d07f5c0cc91a29182c8e8a9aa2bde5b3b2b16519c02f4/flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248", size = 61897, upload-time = "2022-08-03T23:21:25.027Z" }, ] [[package]] name = "flake8" version = "7.1.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", ] dependencies = [ { name = "mccabe", marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, { name = "pycodestyle", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, { name = "pyflakes", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/16/3f2a0bb700ad65ac9663262905a025917c020a3f92f014d2ba8964b4602c/flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd", size = 48119, upload-time = "2025-02-16T18:45:44.296Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/f8/08d37b2cd89da306e3520bd27f8a85692122b42b56c0c2c3784ff09c022f/flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a", size = 57745, upload-time = "2025-02-16T18:45:42.351Z" }, ] [[package]] name = "flake8" version = "7.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "mccabe", marker = "python_full_version >= '3.9'" }, { name = "pycodestyle", version = "2.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pyflakes", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, ] [[package]] name = "funcy" version = "2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/70/b8/c6081521ff70afdff55cd9512b2220bbf4fa88804dae51d1b57b4b58ef32/funcy-2.0.tar.gz", hash = "sha256:3963315d59d41c6f30c04bc910e10ab50a3ac4a225868bfa96feed133df075cb", size = 537931, upload-time = "2023-03-28T06:22:46.764Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl", hash = "sha256:53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0", size = 30891, upload-time = "2023-03-28T06:22:42.576Z" }, ] [[package]] name = "ghp-import" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] [[package]] name = "griffe" version = "1.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "astunparse", marker = "python_full_version < '3.9'" }, { name = "colorama", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/e9/b2c86ad9d69053e497a24ceb25d661094fb321ab4ed39a8b71793dcbae82/griffe-1.4.0.tar.gz", hash = "sha256:8fccc585896d13f1221035d32c50dec65830c87d23f9adb9b1e6f3d63574f7f5", size = 381028, upload-time = "2024-10-11T12:53:54.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/22/7c/e9e66869c2e4c9b378474e49c993128ec0131ef4721038b6d06e50538caf/griffe-1.4.0-py3-none-any.whl", hash = "sha256:e589de8b8c137e99a46ec45f9598fc0ac5b6868ce824b24db09c02d117b89bc5", size = 127015, upload-time = "2024-10-11T12:53:52.383Z" }, ] [[package]] name = "griffe" version = "1.8.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/72/10c5799440ce6f3001b7913988b50a99d7b156da71fe19be06178d5a2dd5/griffe-1.8.0.tar.gz", hash = "sha256:0b4658443858465c13b2de07ff5e15a1032bc889cfafad738a476b8b97bb28d7", size = 401098, upload-time = "2025-07-22T23:45:54.629Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bf/c4/a839fcc28bebfa72925d9121c4d39398f77f95bcba0cf26c972a0cfb1de7/griffe-1.8.0-py3-none-any.whl", hash = "sha256:110faa744b2c5c84dd432f4fa9aa3b14805dd9519777dd55e8db214320593b02", size = 132487, upload-time = "2025-07-22T23:45:52.778Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "importlib-metadata" version = "8.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, ] [[package]] name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "zipp", version = "3.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "jinja2-ansible-filters" version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2", marker = "python_full_version >= '3.11'" }, { name = "pyyaml", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/27/fa186af4b246eb869ffca8ffa42d92b05abaec08c99329e74d88b2c46ec7/jinja2-ansible-filters-1.3.2.tar.gz", hash = "sha256:07c10cf44d7073f4f01102ca12d9a2dc31b41d47e4c61ed92ef6a6d2669b356b", size = 16945, upload-time = "2022-06-30T14:08:50.775Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl", hash = "sha256:e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34", size = 18975, upload-time = "2022-06-30T14:08:49.571Z" }, ] [[package]] name = "markdown" version = "3.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349, upload-time = "2024-08-16T15:55:16.176Z" }, ] [[package]] name = "markdown" version = "3.8.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, ] [[package]] name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "2.1.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206, upload-time = "2024-02-02T16:30:04.105Z" }, { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079, upload-time = "2024-02-02T16:30:06.5Z" }, { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620, upload-time = "2024-02-02T16:30:08.31Z" }, { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818, upload-time = "2024-02-02T16:30:09.577Z" }, { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493, upload-time = "2024-02-02T16:30:11.488Z" }, { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630, upload-time = "2024-02-02T16:30:13.144Z" }, { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745, upload-time = "2024-02-02T16:30:14.222Z" }, { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021, upload-time = "2024-02-02T16:30:16.032Z" }, { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659, upload-time = "2024-02-02T16:30:17.079Z" }, { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213, upload-time = "2024-02-02T16:30:18.251Z" }, { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" }, { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" }, { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" }, { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" }, { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" }, { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" }, { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" }, { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" }, { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" }, { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" }, { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" }, { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" }, { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" }, { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" }, { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" }, { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" }, { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" }, { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192, upload-time = "2024-02-02T16:30:57.715Z" }, { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072, upload-time = "2024-02-02T16:30:58.844Z" }, { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928, upload-time = "2024-02-02T16:30:59.922Z" }, { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106, upload-time = "2024-02-02T16:31:01.582Z" }, { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781, upload-time = "2024-02-02T16:31:02.71Z" }, { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518, upload-time = "2024-02-02T16:31:04.392Z" }, { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669, upload-time = "2024-02-02T16:31:05.53Z" }, { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933, upload-time = "2024-02-02T16:31:06.636Z" }, { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656, upload-time = "2024-02-02T16:31:07.767Z" }, { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206, upload-time = "2024-02-02T16:31:08.843Z" }, { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193, upload-time = "2024-02-02T16:31:10.155Z" }, { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073, upload-time = "2024-02-02T16:31:11.442Z" }, { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486, upload-time = "2024-02-02T16:31:12.488Z" }, { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685, upload-time = "2024-02-02T16:31:13.726Z" }, { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338, upload-time = "2024-02-02T16:31:14.812Z" }, { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439, upload-time = "2024-02-02T16:31:15.946Z" }, { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531, upload-time = "2024-02-02T16:31:17.13Z" }, { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823, upload-time = "2024-02-02T16:31:18.247Z" }, { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658, upload-time = "2024-02-02T16:31:19.583Z" }, { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211, upload-time = "2024-02-02T16:31:20.96Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" }, { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" }, { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" }, { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" }, { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" }, { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" }, { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" }, { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" }, { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" }, { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, ] [[package]] name = "mawk" version = "0.1.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/45/42/0cb0d6d02649f50e2a8918a350152d8d139839cf74befeeeb83668302e48/mawk-0.1.4.tar.gz", hash = "sha256:4e115b2f7eae97406bf2360bafba22efee03d29ff298436a69db506b1535d2f1", size = 7216, upload-time = "2023-06-04T12:53:42.815Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/29/b4ddddd1ae74ce2651e7eedf1871a6b2240433eeec070622f81b985d5e70/mawk-0.1.4-py3-none-any.whl", hash = "sha256:8ab7ce0808d10769f8aa05af8448046c290af5f529db874b44a8fd56056c4462", size = 7941, upload-time = "2023-06-04T12:53:41.112Z" }, ] [[package]] name = "mccabe" version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "mergedeep" version = "1.3.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] [[package]] name = "mkdocs" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "jinja2" }, { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markdown", version = "3.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mergedeep" }, { name = "mkdocs-get-deps" }, { name = "packaging" }, { name = "pathspec" }, { name = "pyyaml" }, { name = "pyyaml-env-tag", version = "0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pyyaml-env-tag", version = "1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "watchdog" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] [[package]] name = "mkdocs-autorefs" version = "1.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mkdocs", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/ae/0f1154c614d6a8b8a36fff084e5b82af3a15f7d2060cf0dcdb1c53297a71/mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f", size = 40262, upload-time = "2024-09-01T18:29:18.514Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f", size = 16522, upload-time = "2024-09-01T18:29:16.605Z" }, ] [[package]] name = "mkdocs-autorefs" version = "1.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "markdown", version = "3.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mkdocs", marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/47/0c/c9826f35b99c67fa3a7cddfa094c1a6c43fafde558c309c6e4403e5b37dc/mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749", size = 54961, upload-time = "2025-05-20T13:09:09.886Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/87/dc/fc063b78f4b769d1956319351704e23ebeba1e9e1d6a41b4b602325fd7e4/mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13", size = 24969, upload-time = "2025-05-20T13:09:08.237Z" }, ] [[package]] name = "mkdocs-entangled-plugin" version = "0.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "mkdocs", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/a2/f94a86faae15f76daf39735f4aed443a9fb5af5e568919f9d472cbc222b5/mkdocs_entangled_plugin-0.2.0.tar.gz", hash = "sha256:7f585cc5811fb097aad0435c20929108348b293830e4b8978b55e19f24631908", size = 8672, upload-time = "2023-05-03T23:56:43.771Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/89/c75092d147093ccf6994bacddd58b40d3fa92092d9ec236356f399a4d44a/mkdocs_entangled_plugin-0.2.0-py3-none-any.whl", hash = "sha256:f386631c11e6c19a41f89902f83275c274db8abacf5adac9cff88394ec9e1788", size = 10821, upload-time = "2023-05-03T23:56:42.589Z" }, ] [[package]] name = "mkdocs-entangled-plugin" version = "0.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", ] dependencies = [ { name = "entangled-cli", marker = "python_full_version >= '3.11'" }, { name = "mawk", marker = "python_full_version >= '3.11'" }, { name = "mkdocs", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/1b/af0757f0a5a60a636903f520ca6164df314068374f765a072d06b65bad7a/mkdocs_entangled_plugin-0.4.0.tar.gz", hash = "sha256:e78f1ff3b55a3838f6a5d6a46d9280484af4520a4679f9bccd8b638406b026c8", size = 7527, upload-time = "2023-10-14T12:20:40.279Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/64/d0/9bf18d41e1e2eda91d67a3493778016001402781dcf9f3c391d94fb50545/mkdocs_entangled_plugin-0.4.0-py3-none-any.whl", hash = "sha256:af4fe84e2218e2c0a805d20161977e34cd56f8af24b4d1a88be4c258176b81bb", size = 9408, upload-time = "2023-10-14T12:20:18.937Z" }, ] [[package]] name = "mkdocs-get-deps" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "mergedeep" }, { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "platformdirs", version = "4.3.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] [[package]] name = "mkdocs-include-markdown-plugin" version = "6.2.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "mkdocs", marker = "python_full_version < '3.9'" }, { name = "wcmatch", version = "10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/fe/4bb438d0f58995f81e2616d640f7efe0df9b1f992cba706a9453676c9140/mkdocs_include_markdown_plugin-6.2.2.tar.gz", hash = "sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d", size = 21045, upload-time = "2024-08-10T23:36:41.503Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/50/d9/7b2b09b4870a2cd5a80628c74553307205a8474aabe128b66e305b56ac30/mkdocs_include_markdown_plugin-6.2.2-py3-none-any.whl", hash = "sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7", size = 24643, upload-time = "2024-08-10T23:36:39.736Z" }, ] [[package]] name = "mkdocs-include-markdown-plugin" version = "7.1.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "mkdocs", marker = "python_full_version >= '3.9'" }, { name = "wcmatch", version = "10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/17/988d97ac6849b196f54d45ca9c60ca894880c160a512785f03834704b3d9/mkdocs_include_markdown_plugin-7.1.6.tar.gz", hash = "sha256:a0753cb82704c10a287f1e789fc9848f82b6beb8749814b24b03dd9f67816677", size = 23391, upload-time = "2025-06-13T18:25:51.193Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e2/a1/6cf1667a05e5f468e1263fcf848772bca8cc9e358cd57ae19a01f92c9f6f/mkdocs_include_markdown_plugin-7.1.6-py3-none-any.whl", hash = "sha256:7975a593514887c18ecb68e11e35c074c5499cfa3e51b18cd16323862e1f7345", size = 27161, upload-time = "2025-06-13T18:25:49.847Z" }, ] [[package]] name = "mkdocs-material" version = "9.6.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, { name = "backrefs", version = "5.7.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "backrefs", version = "5.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "colorama" }, { name = "jinja2" }, { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markdown", version = "3.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mkdocs" }, { name = "mkdocs-material-extensions" }, { name = "paginate" }, { name = "pygments" }, { name = "pymdown-extensions", version = "10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pymdown-extensions", version = "10.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/c1/f804ba2db2ddc2183e900befe7dad64339a34fa935034e1ab405289d0a97/mkdocs_material-9.6.15.tar.gz", hash = "sha256:64adf8fa8dba1a17905b6aee1894a5aafd966d4aeb44a11088519b0f5ca4f1b5", size = 3951836, upload-time = "2025-07-01T10:14:15.671Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1d/30/dda19f0495a9096b64b6b3c07c4bfcff1c76ee0fc521086d53593f18b4c0/mkdocs_material-9.6.15-py3-none-any.whl", hash = "sha256:ac969c94d4fe5eb7c924b6d2f43d7db41159ea91553d18a9afc4780c34f2717a", size = 8716840, upload-time = "2025-07-01T10:14:13.18Z" }, ] [[package]] name = "mkdocs-material-extensions" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, ] [[package]] name = "mkdocstrings" version = "0.26.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "jinja2", marker = "python_full_version < '3.9'" }, { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mkdocs", marker = "python_full_version < '3.9'" }, { name = "mkdocs-autorefs", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pymdown-extensions", version = "10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/170ff04de72227f715d67da32950c7b8434449f3805b2ec3dd1085db4d7c/mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33", size = 92677, upload-time = "2024-09-06T10:26:06.736Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/23/cc/8ba127aaee5d1e9046b0d33fa5b3d17da95a9d705d44902792e0569257fd/mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf", size = 29643, upload-time = "2024-09-06T10:26:04.498Z" }, ] [package.optional-dependencies] python = [ { name = "mkdocstrings-python", version = "1.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] [[package]] name = "mkdocstrings" version = "0.30.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "jinja2", marker = "python_full_version >= '3.9'" }, { name = "markdown", version = "3.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mkdocs", marker = "python_full_version >= '3.9'" }, { name = "mkdocs-autorefs", version = "1.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pymdown-extensions", version = "10.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e2/0a/7e4776217d4802009c8238c75c5345e23014a4706a8414a62c0498858183/mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444", size = 106597, upload-time = "2025-07-22T23:48:45.998Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/de/b4/3c5eac68f31e124a55d255d318c7445840fa1be55e013f507556d6481913/mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2", size = 36579, upload-time = "2025-07-22T23:48:44.152Z" }, ] [package.optional-dependencies] python = [ { name = "mkdocstrings-python", version = "1.16.12", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] [[package]] name = "mkdocstrings-python" version = "1.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "griffe", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mkdocs-autorefs", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mkdocstrings", version = "0.26.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/ba/534c934cd0a809f51c91332d6ed278782ee4126b8ba8db02c2003f162b47/mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322", size = 166890, upload-time = "2024-09-03T17:20:54.904Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2f/f2/2a2c48fda645ac6bbe73bcc974587a579092b6868e6ff8bc6d177f4db38a/mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af", size = 109297, upload-time = "2024-09-03T17:20:52.621Z" }, ] [[package]] name = "mkdocstrings-python" version = "1.16.12" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "griffe", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mkdocs-autorefs", version = "1.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mkdocstrings", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bf/ed/b886f8c714fd7cccc39b79646b627dbea84cd95c46be43459ef46852caf0/mkdocstrings_python-1.16.12.tar.gz", hash = "sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d", size = 206065, upload-time = "2025-06-03T12:52:49.276Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3b/dd/a24ee3de56954bfafb6ede7cd63c2413bb842cc48eb45e41c43a05a33074/mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374", size = 124287, upload-time = "2025-06-03T12:52:47.819Z" }, ] [[package]] name = "mypy" version = "1.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532, upload-time = "2024-10-22T21:55:47.458Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5e/8c/206de95a27722b5b5a8c85ba3100467bd86299d92a4f71c6b9aa448bfa2f/mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", size = 11020731, upload-time = "2024-10-22T21:54:54.221Z" }, { url = "https://files.pythonhosted.org/packages/ab/bb/b31695a29eea76b1569fd28b4ab141a1adc9842edde080d1e8e1776862c7/mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", size = 10184276, upload-time = "2024-10-22T21:54:34.679Z" }, { url = "https://files.pythonhosted.org/packages/a5/2d/4a23849729bb27934a0e079c9c1aad912167d875c7b070382a408d459651/mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", size = 12587706, upload-time = "2024-10-22T21:55:45.309Z" }, { url = "https://files.pythonhosted.org/packages/5c/c3/d318e38ada50255e22e23353a469c791379825240e71b0ad03e76ca07ae6/mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", size = 13105586, upload-time = "2024-10-22T21:55:18.957Z" }, { url = "https://files.pythonhosted.org/packages/4a/25/3918bc64952370c3dbdbd8c82c363804678127815febd2925b7273d9482c/mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", size = 9632318, upload-time = "2024-10-22T21:55:13.791Z" }, { url = "https://files.pythonhosted.org/packages/d0/19/de0822609e5b93d02579075248c7aa6ceaddcea92f00bf4ea8e4c22e3598/mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", size = 10939027, upload-time = "2024-10-22T21:55:31.266Z" }, { url = "https://files.pythonhosted.org/packages/c8/71/6950fcc6ca84179137e4cbf7cf41e6b68b4a339a1f5d3e954f8c34e02d66/mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", size = 10108699, upload-time = "2024-10-22T21:55:34.646Z" }, { url = "https://files.pythonhosted.org/packages/26/50/29d3e7dd166e74dc13d46050b23f7d6d7533acf48f5217663a3719db024e/mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", size = 12506263, upload-time = "2024-10-22T21:54:51.807Z" }, { url = "https://files.pythonhosted.org/packages/3f/1d/676e76f07f7d5ddcd4227af3938a9c9640f293b7d8a44dd4ff41d4db25c1/mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", size = 12984688, upload-time = "2024-10-22T21:55:08.476Z" }, { url = "https://files.pythonhosted.org/packages/9c/03/5a85a30ae5407b1d28fab51bd3e2103e52ad0918d1e68f02a7778669a307/mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", size = 9626811, upload-time = "2024-10-22T21:54:59.152Z" }, { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900, upload-time = "2024-10-22T21:55:37.103Z" }, { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818, upload-time = "2024-10-22T21:55:11.513Z" }, { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275, upload-time = "2024-10-22T21:54:37.694Z" }, { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783, upload-time = "2024-10-22T21:55:42.852Z" }, { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197, upload-time = "2024-10-22T21:54:43.68Z" }, { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721, upload-time = "2024-10-22T21:54:22.321Z" }, { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996, upload-time = "2024-10-22T21:54:46.023Z" }, { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043, upload-time = "2024-10-22T21:55:06.231Z" }, { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996, upload-time = "2024-10-22T21:55:25.811Z" }, { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709, upload-time = "2024-10-22T21:55:21.246Z" }, { url = "https://files.pythonhosted.org/packages/5e/2a/13e9ad339131c0fba5c70584f639005a47088f5eed77081a3d00479df0ca/mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a", size = 10955147, upload-time = "2024-10-22T21:55:39.445Z" }, { url = "https://files.pythonhosted.org/packages/94/39/02929067dc16b72d78109195cfed349ac4ec85f3d52517ac62b9a5263685/mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb", size = 10138373, upload-time = "2024-10-22T21:54:56.889Z" }, { url = "https://files.pythonhosted.org/packages/4a/cc/066709bb01734e3dbbd1375749f8789bf9693f8b842344fc0cf52109694f/mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b", size = 12543621, upload-time = "2024-10-22T21:54:25.798Z" }, { url = "https://files.pythonhosted.org/packages/f5/a2/124df839025348c7b9877d0ce134832a9249968e3ab36bb826bab0e9a1cf/mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74", size = 13050348, upload-time = "2024-10-22T21:54:40.801Z" }, { url = "https://files.pythonhosted.org/packages/45/86/cc94b1e7f7e756a63043cf425c24fb7470013ee1c032180282db75b1b335/mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6", size = 9615311, upload-time = "2024-10-22T21:54:31.74Z" }, { url = "https://files.pythonhosted.org/packages/5f/d4/b33ddd40dad230efb317898a2d1c267c04edba73bc5086bf77edeb410fb2/mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc", size = 11013906, upload-time = "2024-10-22T21:55:28.105Z" }, { url = "https://files.pythonhosted.org/packages/f4/e6/f414bca465b44d01cd5f4a82761e15044bedd1bf8025c5af3cc64518fac5/mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732", size = 10180657, upload-time = "2024-10-22T21:55:03.931Z" }, { url = "https://files.pythonhosted.org/packages/38/e9/fc3865e417722f98d58409770be01afb961e2c1f99930659ff4ae7ca8b7e/mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc", size = 12586394, upload-time = "2024-10-22T21:54:49.173Z" }, { url = "https://files.pythonhosted.org/packages/2e/35/f4d8b6d2cb0b3dad63e96caf159419dda023f45a358c6c9ac582ccaee354/mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d", size = 13103591, upload-time = "2024-10-22T21:55:01.642Z" }, { url = "https://files.pythonhosted.org/packages/22/1d/80594aef135f921dd52e142fa0acd19df197690bd0cde42cea7b88cf5aa2/mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24", size = 9634690, upload-time = "2024-10-22T21:54:28.814Z" }, { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043, upload-time = "2024-10-22T21:55:16.617Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] name = "paginate" version = "0.5.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] [[package]] name = "pip" version = "25.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/70/53/b309b4a497b09655cb7e07088966881a57d082f48ac3cb54ea729fd2c6cf/pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea", size = 1950850, upload-time = "2025-02-09T17:14:04.423Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c9/bc/b7db44f5f39f9d0494071bddae6880eb645970366d0a200022a1a93d57f5/pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f", size = 1841526, upload-time = "2025-02-09T17:14:01.463Z" }, ] [[package]] name = "pip" version = "25.1.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/59/de/241caa0ca606f2ec5fe0c1f4261b0465df78d786a38da693864a116c37f4/pip-25.1.1.tar.gz", hash = "sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077", size = 1940155, upload-time = "2025-05-02T15:14:02.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl", hash = "sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af", size = 1825227, upload-time = "2025-05-02T15:13:59.102Z" }, ] [[package]] name = "platformdirs" version = "4.3.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" }, ] [[package]] name = "platformdirs" version = "4.3.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "plumbum" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "python_full_version >= '3.11' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f0/5d/49ba324ad4ae5b1a4caefafbce7a1648540129344481f2ed4ef6bb68d451/plumbum-1.9.0.tar.gz", hash = "sha256:e640062b72642c3873bd5bdc3effed75ba4d3c70ef6b6a7b907357a84d909219", size = 319083, upload-time = "2024-10-05T05:59:27.059Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/9d/d03542c93bb3d448406731b80f39c3d5601282f778328c22c77d270f4ed4/plumbum-1.9.0-py3-none-any.whl", hash = "sha256:9fd0d3b0e8d86e4b581af36edf3f3bbe9d1ae15b45b8caab28de1bcb27aaa7f5", size = 127970, upload-time = "2024-10-05T05:59:25.102Z" }, ] [[package]] name = "prompt-toolkit" version = "3.0.51" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, ] [[package]] name = "ptyprocess" version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, ] [[package]] name = "pycodestyle" version = "2.9.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/b6/83/5bcaedba1f47200f0665ceb07bcb00e2be123192742ee0edfb66b600e5fd/pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", size = 102127, upload-time = "2022-08-03T23:13:29.715Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/67/e4/fc77f1039c34b3612c4867b69cbb2b8a4e569720b1f19b0637002ee03aff/pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b", size = 41493, upload-time = "2022-08-03T23:13:27.416Z" }, ] [[package]] name = "pycodestyle" version = "2.12.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232, upload-time = "2024-08-04T20:26:54.576Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284, upload-time = "2024-08-04T20:26:53.173Z" }, ] [[package]] name = "pycodestyle" version = "2.14.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, ] [[package]] name = "pydantic" version = "2.11.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types", marker = "python_full_version >= '3.11'" }, { name = "pydantic-core", marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-inspection", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] [[package]] name = "pydantic-core" version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, { url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" }, { url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" }, { url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" }, { url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" }, { url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" }, { url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" }, { url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" }, { url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" }, { url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" }, { url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" }, { url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" }, { url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" }, { url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" }, { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, { url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" }, { url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" }, { url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" }, { url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" }, { url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" }, { url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" }, { url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" }, { url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" }, { url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" }, ] [[package]] name = "pyflakes" version = "2.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/07/92/f0cb5381f752e89a598dd2850941e7f570ac3cb8ea4a344854de486db152/pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3", size = 66388, upload-time = "2022-07-30T17:29:05.816Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/dc/13/63178f59f74e53acc2165aee4b002619a3cfa7eeaeac989a9eb41edf364e/pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", size = 66116, upload-time = "2022-07-30T17:29:04.179Z" }, ] [[package]] name = "pyflakes" version = "3.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788, upload-time = "2024-01-05T00:28:47.703Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" }, ] [[package]] name = "pyflakes" version = "3.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pymdown-extensions" version = "10.15" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pyyaml", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/92/a7296491dbf5585b3a987f3f3fc87af0e632121ff3e490c14b5f2d2b4eb5/pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7", size = 852320, upload-time = "2025-04-27T23:48:29.183Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f", size = 265845, upload-time = "2025-04-27T23:48:27.359Z" }, ] [[package]] name = "pymdown-extensions" version = "10.16" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "markdown", version = "3.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pyyaml", marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/0a/c06b542ac108bfc73200677309cd9188a3a01b127a63f20cadc18d873d88/pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de", size = 853197, upload-time = "2025-06-21T17:56:36.974Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/98/d4/10bb14004d3c792811e05e21b5e5dcae805aacb739bd12a0540967b99592/pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2", size = 266143, upload-time = "2025-06-21T17:56:35.356Z" }, ] [[package]] name = "pyproject-hooks" version = "1.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] [[package]] name = "pytest" version = "8.3.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, { name = "iniconfig", marker = "python_full_version < '3.9'" }, { name = "packaging", marker = "python_full_version < '3.9'" }, { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "tomli", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] name = "pytest" version = "8.4.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, { name = "iniconfig", marker = "python_full_version >= '3.9'" }, { name = "packaging", marker = "python_full_version >= '3.9'" }, { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pygments", marker = "python_full_version >= '3.9'" }, { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "pytest-timeout" version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, ] [[package]] name = "python-dateutil" version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "pytz" version = "2025.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, { url = "https://files.pythonhosted.org/packages/75/20/6cd04d636a4c83458ecbb7c8220c13786a1a80d3f5fb568df39310e73e98/pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c", size = 8766775, upload-time = "2025-07-14T20:12:55.029Z" }, { url = "https://files.pythonhosted.org/packages/ff/6c/94c10268bae5d0d0c6509bdfb5aa08882d11a9ccdf89ff1cde59a6161afb/pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd", size = 9594743, upload-time = "2025-07-14T20:12:57.59Z" }, { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" }, { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" }, { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218, upload-time = "2024-08-06T20:33:06.411Z" }, { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067, upload-time = "2024-08-06T20:33:07.879Z" }, { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812, upload-time = "2024-08-06T20:33:12.542Z" }, { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531, upload-time = "2024-08-06T20:33:14.391Z" }, { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820, upload-time = "2024-08-06T20:33:16.586Z" }, { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514, upload-time = "2024-08-06T20:33:22.414Z" }, { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702, upload-time = "2024-08-06T20:33:23.813Z" }, { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, ] [[package]] name = "pyyaml-env-tag" version = "0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "pyyaml", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631, upload-time = "2020-11-12T02:38:26.239Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911, upload-time = "2020-11-12T02:38:24.638Z" }, ] [[package]] name = "pyyaml-env-tag" version = "1.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "pyyaml", marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] [[package]] name = "questionary" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/b8/d16eb579277f3de9e56e5ad25280fab52fc5774117fb70362e8c2e016559/questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587", size = 26775, upload-time = "2024-12-29T11:49:17.802Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747, upload-time = "2024-12-29T11:49:16.734Z" }, ] [[package]] name = "requests" version = "2.32.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] name = "rich" version = "13.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, ] [[package]] name = "rich-argparse" version = "1.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/71/a6/34460d81e5534f6d2fc8e8d91ff99a5835fdca53578eac89e4f37b3a7c6d/rich_argparse-1.7.1.tar.gz", hash = "sha256:d7a493cde94043e41ea68fb43a74405fa178de981bf7b800f7a3bd02ac5c27be", size = 38094, upload-time = "2025-05-25T20:20:35.335Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/31/f6/5fc0574af5379606ffd57a4b68ed88f9b415eb222047fe023aefcc00a648/rich_argparse-1.7.1-py3-none-any.whl", hash = "sha256:a8650b42e4a4ff72127837632fba6b7da40784842f08d7395eb67a9cbd7b4bf9", size = 25357, upload-time = "2025-05-25T20:20:33.793Z" }, ] [[package]] name = "ruff" version = "0.12.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373, upload-time = "2025-08-07T19:05:47.268Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315, upload-time = "2025-08-07T19:05:06.15Z" }, { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653, upload-time = "2025-08-07T19:05:09.759Z" }, { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690, upload-time = "2025-08-07T19:05:12.551Z" }, { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923, upload-time = "2025-08-07T19:05:14.821Z" }, { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612, upload-time = "2025-08-07T19:05:16.712Z" }, { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745, upload-time = "2025-08-07T19:05:18.709Z" }, { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885, upload-time = "2025-08-07T19:05:21.025Z" }, { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381, upload-time = "2025-08-07T19:05:23.423Z" }, { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271, upload-time = "2025-08-07T19:05:25.507Z" }, { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783, upload-time = "2025-08-07T19:05:28.14Z" }, { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672, upload-time = "2025-08-07T19:05:30.413Z" }, { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626, upload-time = "2025-08-07T19:05:32.492Z" }, { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162, upload-time = "2025-08-07T19:05:34.449Z" }, { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212, upload-time = "2025-08-07T19:05:36.541Z" }, { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382, upload-time = "2025-08-07T19:05:38.468Z" }, { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482, upload-time = "2025-08-07T19:05:40.391Z" }, { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" }, ] [[package]] name = "setuptools" version = "75.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/5c/01/771ea46cce201dd42cff043a5eea929d1c030fb3d1c2ee2729d02ca7814c/setuptools-75.3.2.tar.gz", hash = "sha256:3c1383e1038b68556a382c1e8ded8887cd20141b0eb5708a6c8d277de49364f5", size = 1354489, upload-time = "2025-03-12T00:02:19.004Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/15/65/3f0dba35760d902849d39d38c0a72767794b1963227b69a587f8a336d08c/setuptools-75.3.2-py3-none-any.whl", hash = "sha256:90ab613b6583fc02d5369cbca13ea26ea0e182d1df2d943ee9cbe81d4c61add9", size = 1251198, upload-time = "2025-03-12T00:02:17.554Z" }, ] [[package]] name = "setuptools" version = "80.9.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] [[package]] name = "setuptools-scm" source = { editable = "." } dependencies = [ { name = "packaging" }, { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "setuptools", version = "80.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, ] [package.optional-dependencies] rich = [ { name = "rich" }, ] [package.dev-dependencies] docs = [ { name = "mkdocs" }, { name = "mkdocs-entangled-plugin", version = "0.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "mkdocs-entangled-plugin", version = "0.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "mkdocs-include-markdown-plugin", version = "6.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "mkdocs-include-markdown-plugin", version = "7.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mkdocs-material" }, { name = "mkdocstrings", version = "0.26.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.9'" }, { name = "mkdocstrings", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.9'" }, { name = "pygments" }, ] test = [ { name = "build" }, { name = "flake8", version = "5.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8.1'" }, { name = "flake8", version = "7.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, { name = "flake8", version = "7.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "griffe", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "griffe", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "mypy" }, { name = "pip", version = "25.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pip", version = "25.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pytest-timeout" }, { name = "rich" }, { name = "ruff" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, { name = "wheel" }, ] [package.metadata] requires-dist = [ { name = "packaging", specifier = ">=20" }, { name = "rich", marker = "extra == 'rich'" }, { name = "setuptools" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=1" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] provides-extras = ["rich", "simple", "toml"] [package.metadata.requires-dev] docs = [ { name = "mkdocs" }, { name = "mkdocs-entangled-plugin" }, { name = "mkdocs-include-markdown-plugin" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extras = ["python"] }, { name = "pygments" }, ] test = [ { name = "build" }, { name = "flake8" }, { name = "griffe" }, { name = "mypy", specifier = "~=1.13.0" }, { name = "pip" }, { name = "pytest" }, { name = "pytest-timeout" }, { name = "rich" }, { name = "ruff" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "wheel" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] name = "tomlkit" version = "0.12.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2b/ab/18f4c8f2bec75eb1a7aebcc52cdb02ab04fd39ff7025bb1b1c7846cc45b8/tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c", size = 191420, upload-time = "2024-05-08T13:50:19.363Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/6d/b5406752c4e4ba86692b22fab0afed8b48f16bdde8f92e1d852976b61dc6/tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f", size = 37685, upload-time = "2024-05-08T13:50:17.343Z" }, ] [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] name = "typing-extensions" version = "4.14.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] name = "typing-inspection" version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] [[package]] name = "urllib3" version = "2.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" }, ] [[package]] name = "urllib3" version = "2.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] name = "watchdog" version = "3.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/95/a6/d6ef450393dac5734c63c40a131f66808d2e6f59f6165ab38c98fbe4e6ec/watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9", size = 124593, upload-time = "2023-03-20T09:21:11.367Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/06/fd/58b82550ebe4883bb2a5e1b6c14d8702b5ce0f36c58470bba51dc777df46/watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41", size = 100697, upload-time = "2023-03-20T09:20:25.047Z" }, { url = "https://files.pythonhosted.org/packages/92/dd/42f47ffdfadff4c41b89c54163f323f875eb963bf90088e477c43b8f7b15/watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397", size = 91219, upload-time = "2023-03-20T09:20:26.864Z" }, { url = "https://files.pythonhosted.org/packages/9b/39/30bb3c2e4f8e89b5c60e98589acf5c5a001cb0efde249aa05d748d1734a2/watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96", size = 91756, upload-time = "2023-03-20T09:20:28.309Z" }, { url = "https://files.pythonhosted.org/packages/00/9e/a9711f35f1ad6571e92dc2e955e7de9dfac21a1b33e9cd212f066a60a387/watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae", size = 100700, upload-time = "2023-03-20T09:20:29.847Z" }, { url = "https://files.pythonhosted.org/packages/84/ab/67001e62603bf2ea35ace40023f7c74f61e8b047160d6bb078373cec1a67/watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9", size = 91251, upload-time = "2023-03-20T09:20:31.892Z" }, { url = "https://files.pythonhosted.org/packages/58/db/d419fdbd3051b42b0a8091ddf78f70540b6d9d277a84845f7c5955f9de92/watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7", size = 91753, upload-time = "2023-03-20T09:20:33.337Z" }, { url = "https://files.pythonhosted.org/packages/7f/6e/7ca8ed16928d7b11da69372f55c64a09dce649d2b24b03f7063cd8683c4b/watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f", size = 100655, upload-time = "2023-03-20T09:20:37.473Z" }, { url = "https://files.pythonhosted.org/packages/2e/54/48527f3aea4f7ed331072352fee034a7f3d6ec7a2ed873681738b2586498/watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc", size = 91216, upload-time = "2023-03-20T09:20:39.793Z" }, { url = "https://files.pythonhosted.org/packages/dc/89/3a3ce6dd01807ff918aec3bbcabc92ed1a7edc5bb2266c720bb39fec1bec/watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3", size = 91752, upload-time = "2023-03-20T09:20:41.395Z" }, { url = "https://files.pythonhosted.org/packages/75/fe/d9a37d8df76878853f68dd665ec6d2c7a984645de460164cb880a93ffe6b/watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3", size = 100653, upload-time = "2023-03-20T09:20:42.936Z" }, { url = "https://files.pythonhosted.org/packages/94/ce/70c65a6c4b0330129c402624d42f67ce82d6a0ba2036de67628aeffda3c1/watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0", size = 91247, upload-time = "2023-03-20T09:20:45.157Z" }, { url = "https://files.pythonhosted.org/packages/51/b9/444a984b1667013bac41b31b45d9718e069cc7502a43a924896806605d83/watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8", size = 91753, upload-time = "2023-03-20T09:20:46.913Z" }, { url = "https://files.pythonhosted.org/packages/ea/76/bef1c6f6ac18041234a9f3e8bc995d611e255c44f10433bfaf255968c269/watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346", size = 90419, upload-time = "2023-03-20T09:20:50.715Z" }, { url = "https://files.pythonhosted.org/packages/30/65/9e36a3c821d47a22e54a8fc73681586b2d26e82d24ea3af63acf2ef78f97/watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64", size = 90428, upload-time = "2023-03-20T09:20:52.216Z" }, { url = "https://files.pythonhosted.org/packages/92/28/631872d7fbc45527037060db8c838b47a129a6c09d2297d6dddcfa283cf2/watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a", size = 82049, upload-time = "2023-03-20T09:20:53.951Z" }, { url = "https://files.pythonhosted.org/packages/c0/a2/4e3230bdc1fb878b152a2c66aa941732776f4545bd68135d490591d66713/watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44", size = 82049, upload-time = "2023-03-20T09:20:55.583Z" }, { url = "https://files.pythonhosted.org/packages/21/72/46fd174352cd88b9157ade77e3b8835125d4b1e5186fc7f1e8c44664e029/watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a", size = 82052, upload-time = "2023-03-20T09:20:57.124Z" }, { url = "https://files.pythonhosted.org/packages/74/3c/e4b77f4f069aca2b6e35925db7a1aa6cb600dcb52fc3e962284640ca37f3/watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709", size = 82050, upload-time = "2023-03-20T09:20:58.864Z" }, { url = "https://files.pythonhosted.org/packages/71/3a/b12740f4f60861240d57b42a2ac6ac0a2821db506c4435f7872c1fad867d/watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83", size = 82050, upload-time = "2023-03-20T09:21:00.452Z" }, { url = "https://files.pythonhosted.org/packages/40/1b/4e6d3e0f587587931f590531b4ed08070d71a9efb35541d792a68d8ee593/watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d", size = 82049, upload-time = "2023-03-20T09:21:01.979Z" }, { url = "https://files.pythonhosted.org/packages/2b/f0/456948b865ab259784f774154e7d65844fa9757522fdb11533fbf8ae7aca/watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33", size = 82051, upload-time = "2023-03-20T09:21:03.67Z" }, { url = "https://files.pythonhosted.org/packages/55/0d/bfc2a0d425b12444a2dc245a934c065bbb7bd9833fff071cba79c21bb76e/watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f", size = 82038, upload-time = "2023-03-20T09:21:05.492Z" }, { url = "https://files.pythonhosted.org/packages/9b/6e/ce8d124d03cd3f2941365d9c81d62e3afe43f2dc7e6e86274fa9c2ec2d5b/watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c", size = 82040, upload-time = "2023-03-20T09:21:07.609Z" }, { url = "https://files.pythonhosted.org/packages/ba/0c/cd0337069c468f22ef256e768ece74c78b511092f1004ab260268e1af4a9/watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759", size = 82040, upload-time = "2023-03-20T09:21:09.178Z" }, ] [[package]] name = "wcmatch" version = "10.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] dependencies = [ { name = "bracex", version = "2.5.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/ab/b3a52228538ccb983653c446c1656eddf1d5303b9cb8b9aef6a91299f862/wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a", size = 115578, upload-time = "2024-09-26T18:39:52.505Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/df/4ee467ab39cc1de4b852c212c1ed3becfec2e486a51ac1ce0091f85f38d7/wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a", size = 39347, upload-time = "2024-09-26T18:39:51.002Z" }, ] [[package]] name = "wcmatch" version = "10.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ { name = "bracex", version = "2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, ] [[package]] name = "wcwidth" version = "0.2.13" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] [[package]] name = "wheel" version = "0.45.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, ] [[package]] name = "zipp" version = "3.20.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.8.1' and python_full_version < '3.9'", "python_full_version < '3.8.1'", ] sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ]