pax_global_header00006660000000000000000000000064151546513630014523gustar00rootroot0000000000000052 comment=7188740143de900230f12ee5be9a41dd6154dd58 cogent3-citeable-7188740/000077500000000000000000000000001515465136300150375ustar00rootroot00000000000000cogent3-citeable-7188740/.github/000077500000000000000000000000001515465136300163775ustar00rootroot00000000000000cogent3-citeable-7188740/.github/dependabot.yml000066400000000000000000000004631515465136300212320ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: weekly time: "19:00" open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" time: "19:00" open-pull-requests-limit: 10 cogent3-citeable-7188740/.github/workflows/000077500000000000000000000000001515465136300204345ustar00rootroot00000000000000cogent3-citeable-7188740/.github/workflows/linters.yml000066400000000000000000000012671515465136300226450ustar00rootroot00000000000000name: Lint code using ruff on: push: permissions: contents: write jobs: linters: runs-on: ubuntu-latest if: github.repository == 'cogent3/citeable' steps: - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: Format code using ruff run: uv run nox -s fmt - name: Commit changes uses: EndBug/add-and-commit@v9 with: author_name: ${{ github.actor }} author_email: ${{ github.actor }}@users.noreply.github.com message: "STY: pre-commit linting with ruff" add: "." cogent3-citeable-7188740/.github/workflows/release.yml000066400000000000000000000040641515465136300226030ustar00rootroot00000000000000name: Release on: [workflow_dispatch] jobs: test: name: "Test on Python ${{ matrix.python-version }} (${{ matrix.os }})" runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: "actions/checkout@v6" with: fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: "Run nox for ${{ matrix.python-version }}" shell: bash run: uv run nox -s "test-${{ matrix.python-version }}" build: name: Build wheel and sdist needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v7 - name: Build sdist and wheel run: uv build - name: Upload sdist and wheel uses: actions/upload-artifact@v7 with: name: citeable-wheel-sdist path: | ./dist/*.whl ./dist/*.tar.gz release_test: name: Release to Test PyPI needs: build environment: test_pypi runs-on: ubuntu-latest permissions: id-token: write steps: - name: Download sdist and wheel uses: actions/download-artifact@v8 with: name: citeable-wheel-sdist path: ./dist - name: Publish package distributions to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ release: name: Release to PyPI needs: release_test environment: pypi runs-on: ubuntu-latest permissions: id-token: write steps: - name: Download sdist and wheel uses: actions/download-artifact@v8 with: name: citeable-wheel-sdist path: ./dist - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 cogent3-citeable-7188740/.github/workflows/testing_develop.yml000066400000000000000000000034761515465136300243640ustar00rootroot00000000000000name: CI on: push: pull_request: # NOTE: # if changing python versions, also update versions in # - release.yml # - noxfile.py jobs: tests: name: "Python ${{ matrix.python-version }} (${{ matrix.os }})" runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.11", "3.14"] steps: - uses: "actions/checkout@v6" with: fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: "Run nox for ${{ matrix.python-version }}" shell: bash run: | cov="--cov-report lcov:lcov-${{matrix.os}}-${{matrix.python-version}}.lcov --cov-report term --cov-append --cov citeable" uv run nox -s "test-${{ matrix.python-version }}" -- $cov - name: Coveralls Parallel uses: coverallsapp/github-action@v2 with: parallel: true github-token: ${{ secrets.github_token }} flag-name: run-${{matrix.python-version}}-${{matrix.os}} file: "tests/lcov-${{matrix.os}}-${{matrix.python-version}}.lcov" type_check: name: Type Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: "Run Type Checking" run: uv run mypy src/citeable --strict finish: name: "Finish Coveralls" needs: tests runs-on: ubuntu-latest steps: - name: Coveralls Finished uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} parallel-finished: true cogent3-citeable-7188740/.gitignore000066400000000000000000000011021515465136300170210ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 doc/_build # docker stuff .devcontainer/* # Installer logs pip-log.txt # Unit test / coverage reports .mypy* **/.mypy_cache/* .coverage* .tox .nox coverage.xml junit-*.xml nosetests.xml tests/draw_results lcov*.info .ruff_cache/* # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea/* .DS_Store __pycache__ *.code-workspace *.wpr *.wpu .vscode/* venv* .venv* working/ *.ipynb *.ipynb_checkpoints/ # vi .*.swp cogent3-citeable-7188740/.hgignore000066400000000000000000000006461515465136300166500ustar00rootroot00000000000000syntax:glob .svn *.pyc *.pyo *.so *.o *.DS_Store *.tmproj *.rej *.orig *.wpr *.pdf _build/* build *htmlcov* *.idea draw_results *.coverage* *egg-info* *.wpu .cache* *taskpaper *.ipynb *.ipynb_checkpoints* *.sublime* *.patch *.pytest_cache *.tox *.nox *.vscode *.code-workspace coverage.xml __pycache__ junit-*.xml doc/draw* dist/* working/* lcov*.info .ruff_cache/* .devcontainer/* hg_old*zip hg_old/* venv* .venv* .mypy* cogent3-citeable-7188740/.sourcery.yaml000066400000000000000000000000501515465136300176470ustar00rootroot00000000000000rule_settings: python_version: '3.11' cogent3-citeable-7188740/LICENSE000066400000000000000000000027341515465136300160520ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2026, Gavin Huttley Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cogent3-citeable-7188740/README.md000066400000000000000000000320461515465136300163230ustar00rootroot00000000000000# citeable 📚 *Structured BibTeX citations for developers of scientific software packages.* [![License](https://img.shields.io/pypi/l/citeable.svg)](https://github.com/cogent3/citeable/blob/main/LICENSE) [![Coverage Status](https://coveralls.io/repos/github/GavinHuttley/citeable/badge.svg?branch=main)](https://coveralls.io/github/GavinHuttley/citeable?branch=main) [![PyPI version](https://badge.fury.io/py/citeable.svg)](https://badge.fury.io/py/citeable) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cogent3) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![CodeQL](https://github.com/cogent3/cogent3/actions/workflows/codeql.yml/badge.svg)](https://github.com/cogent3/cogent3/actions/workflows/codeql.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ec0f6a2dad174b04b5dcbfdae02acab7)](https://app.codacy.com/gh/cogent3/citeable/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) ## Overview `citeable` is a lightweight, zero-dependency, pure-Python library for defining structured bibliographic citations. The goal is to make it easy for package users to cite package developers. It is being used by [cogent3](https://github.com/cogent3/cogent3) and cogent3 plugin developers to declare citations that cogent3 can assemble into a BibTeX-compatible `.bib` file to ensure users cite their work. But it can be used for other projects too! ## Installation ```bash pip install citeable ```
Requirements Pure Python, no dependencies. Requires Python >= 3.11.
## Developer quick start ```python from citeable import Article cite = Article( author=["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"], title="diverse-seq: an application for alignment-free selecting and clustering biological sequences", journal="Journal of Open Source Software", year=2025, volume=10, number=110, pages="7765", doi="10.21105/joss.07765", url="https://doi.org/10.21105/joss.07765", ) # cite.key == 'Huttley.2025' ``` ```bibtex @article{Huttley.2025, author = {Huttley, Gavin and Caley, Katherine and McArthur, Robert}, title = {diverse-seq: an application for alignment-free selecting and clustering biological sequences}, journal = {Journal of Open Source Software}, year = {2025}, volume = {10}, number = {110}, pages = {7765}, doi = {10.21105/joss.07765}, url = {https://doi.org/10.21105/joss.07765}, } ``` ## Defining a citation
Constructing directly in Python (recommended) Citations are constructed directly in Python source. Required fields are positional-or-keyword constructor arguments; optional fields are keyword-only with `None` defaults. `key` is always optional at construction -- it will be auto-generated if omitted (see [Key generation](#key-generation) below). ```python from citeable import Article, Software cite = Article( author=["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"], title="diverse-seq: an application for alignment-free selecting and clustering biological sequences", journal="Journal of Open Source Software", year=2025, volume=10, number=110, pages="7765", doi="10.21105/joss.07765", ) # cite.key == "Huttley.2025" tool_cite = Software( author=["Smith, Jane"], title="my-cogent3-plugin", year=2024, version="1.0.0", url="https://github.com/jsmith/my-cogent3-plugin", ) # tool_cite.key == "Smith.2024" ``` Validation is performed at construction time. A missing required field raises `ValueError` with a message identifying the field and entry type: ``` ValueError: Article requires 'volume'; received None ```
Parsing from a BibTeX string `from_bibtex_string` accepts a raw BibTeX string containing a single record and returns the corresponding `citeable` object. This is the intended path for developers who already have a `.bib` entry in a reference manager -- paste the raw BibTeX string directly into Python source. ```python from citeable import from_bibtex_string cite = from_bibtex_string(""" @article{Huttley.2025, doi = {10.21105/joss.07765}, url = {https://doi.org/10.21105/joss.07765}, year = {2025}, volume = {10}, number = {110}, pages = {7765}, author = {Huttley, Gavin and Caley, Katherine and McArthur, Robert}, title = {diverse-seq: an application for alignment-free selecting and clustering biological sequences}, journal = {Journal of Open Source Software}, } """) ``` The cite key from the BibTeX string is preserved as the `key` value. Author names in `"First Last"` format are normalised to `"Last, First"` on parse. **Round-trip scaffolding:** use `from_bibtex_string` + `repr()` to convert a BibTeX record into a clean Python constructor call, then paste that into your source: ```python print(repr(cite)) ``` ```python Article( author=['Huttley, Gavin', 'Caley, Katherine', 'McArthur, Robert'], title='diverse-seq: an application for alignment-free selecting and clustering biological sequences', year=2025, journal='Journal of Open Source Software', volume=10, pages='7765', number=110, doi='10.21105/joss.07765', url='https://doi.org/10.21105/joss.07765', ) ```
## Supported entry types | Class | BibTeX `@type` | Required fields (beyond common) | |---|---|---| | `Article` | `@article` | `journal`, `volume`, `pages` or `article_number` | | `Book` | `@book` | `publisher` | | `InProceedings` | `@inproceedings` | `booktitle` | | `TechReport` | `@techreport` | `institution` | | `Thesis` | `@phdthesis` / `@mastersthesis` | `school`, `thesis_type` | | `Software` | `@software` | *(none)* | | `Misc` | `@misc` | *(none)* | All types share common fields: `author` (required), `title` (required), `year` (required), `doi`, `url`, `note`, `key`, `app`.
Field reference ### Fields common to all entry types | Field | Required | Notes | |---|---|---| | `key` | No | Auto-generated if not supplied | | `author` | Yes | List of strings in `"Surname, Given"` format | | `title` | Yes | | | `year` | Yes | Integer | | `doi` | No | Recommended where available | | `url` | No | | | `note` | No | | | `app` | No | Name of the cogent3 app. Not written to BibTeX output; excluded from equality and hashing. | ### `Article` | Field | Required | |---|---| | `journal` | Yes | | `volume` | Yes | | `pages` | Yes (or `article_number`) | | `article_number` | No | | `number` | No -- issue number | ### `Book` | Field | Required | |---|---| | `publisher` | Yes | | `edition` | No | | `editor` | No -- list of strings | ### `InProceedings` | Field | Required | |---|---| | `booktitle` | Yes | | `pages` | No | | `publisher` | No | | `editor` | No | ### `TechReport` | Field | Required | |---|---| | `institution` | Yes | | `number` | No -- report number | ### `Thesis` | Field | Required | Notes | |---|---|---| | `school` | Yes | | | `thesis_type` | Yes | `"phd"` or `"masters"` -- determines BibTeX entry type | ### `Software` | Field | Required | |---|---| | `publisher` | No -- organisation or individual releasing the software | | `version` | No -- strongly recommended | | `license` | No | ### `Misc` No additional required fields beyond the common set.
## Key generation Keys are auto-generated from the first author's surname and the year, e.g. `"Huttley.2025"`: 1. Extract surname from the first author (before the first comma, or the last token) 2. Strip non-ASCII characters and spaces; title-case the result 3. Return `"{surname}.{year}"` On collision, `assign_unique_keys` appends a lowercase letter suffix: `"Smith.2024.a"`, `"Smith.2024.b"`, etc. A developer may supply an explicit `key` at construction time, in which case auto-generation is skipped. But note that the key attribute of a citation can be modified and cogent3 will do this if their are key conflicts. ## Working with collections
Assigning unique keys Because citations come from multiple independent plugin developers, key collisions are expected. The function `assign_unique_keys` resolves collisions in-place across a deduplicated list: ```python from citeable import assign_unique_keys unique = assign_unique_keys(citations) ``` - Deduplication by value is performed first: if two objects compare as equal, only the first is retained - Keys already unique in the deduplicated collection are left unchanged - Collisions between distinct citations sharing a base key get a letter suffix: `"Smith.2024"` becomes `"Smith.2024.a"`, `"Smith.2024.b"`, etc. - The function mutates surviving objects in-place and returns the deduplicated list cogent3 calls `assign_unique_keys` when assembling a bibliography from a composed app, so plugin developers do not need to call it themselves.
Writing a .bib file `write_bibtex` takes a list of citations and a file path, deduplicates the list, assigns unique keys, then writes the result as a valid `.bib` file: ```python from citeable import write_bibtex write_bibtex(citations, "bibliography.bib") ``` For cases where only the string is needed: ```python unique = assign_unique_keys(citations) bib_string = "\n\n".join(str(c) for c in unique) ```
## Using citeable with cogent3 The `define_app` decorator in cogent3 has an optional `cite` argument: ```python @define_app(cite=Article(...)) class MyPlugin: ... ``` cogent3 collects citations across a composed app and expose a method (e.g. `app.bibliography()`) that returns a combined `.bib` string. ## Distribution guidance Plugin developers **must** define their citation as a Python object in their package source. This guarantees it is present after `pip install` without any special `package_data` configuration or `MANIFEST.in` entries. The recommended pattern is a dedicated `citations.py` in the plugin package: ``` my_plugin/ __init__.py citations.py # citation objects defined here app.py # @define_app(cite=MY_CITE) used here ``` `from_bibtex_string` is provided as a convenience constructor only. Either way, the result is a Python object embedded in source, not a runtime file read.
Developer setup (uv) This project uses [uv](https://docs.astral.sh/uv/) for dependency management. ### Initial setup ```bash uv sync ``` This creates a `.venv` and installs the package in editable mode with all dev dependencies. ### Running tests ```bash uv run pytest ``` ### Running nox (multi-version test matrix) ```bash uv run nox ``` Nox is configured to use uv as its virtualenv backend, so it will use uv to create per-session environments. ### Formatting ```bash uv run nox -s fmt ``` This runs `ruff check --fix-only` followed by `ruff format`. ### Other common commands ```bash uv run mypy src/citeable --strict # type checking uv run ruff check . # linting uv run cog -r README.md # regenerate cog blocks ```
## Contributing Bug reports and pull requests are welcome at https://github.com/cogent3/citeable. ## Licence BSD-3-Clause. See [LICENSE](https://github.com/cogent3/citeable/blob/main/LICENSE). cogent3-citeable-7188740/noxfile.py000066400000000000000000000012561515465136300170610ustar00rootroot00000000000000import os import sys import nox # on python >= 3.12 this will improve speed of test coverage a lot if sys.version_info >= (3, 12): os.environ["COVERAGE_CORE"] = "sysmon" nox.options.default_venv_backend = "uv" _py_versions = range(11, 15) @nox.session(python=False) def fmt(session: nox.Session) -> None: session.run("ruff", "check", "--fix-only", ".", external=True) session.run("ruff", "format", ".", external=True) @nox.session(python=[f"3.{v}" for v in _py_versions]) def test(session: nox.Session) -> None: session.install("-e.[dev]") session.chdir("tests") session.run( "pytest", "-s", "-x", *session.posargs, ) cogent3-citeable-7188740/pyproject.toml000066400000000000000000000020621515465136300177530ustar00rootroot00000000000000[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "citeable" version = "2026.3.11b1" description = "Structured BibTeX citations for developers of scientific software packages." readme = "README.md" requires-python = ">=3.11" license = { text = "BSD-3-Clause" } dependencies = [] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: OS Independent", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Typing :: Typed", ] [dependency-groups] dev = ["cogapp", "mypy", "nox", "pytest", "pytest-cov", "ruff"] [tool.hatch.build.targets.wheel] packages = ["src/citeable"] [tool.mypy] strict = true [[tool.mypy.overrides]] module = "tests.*" ignore_errors = true [tool.pytest.ini_options] testpaths = ["tests"] cogent3-citeable-7188740/ruff.toml000066400000000000000000000046301515465136300167010ustar00rootroot00000000000000exclude = [ ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".ipynb_checkpoints", ".mypy_cache", ".nox", ".pants.d", ".pyenv", ".pytest_cache", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", ".vscode", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "site-packages", "venv", "working", ] # Same as Black. line-length = 88 indent-width = 4 target-version = "py311" [lint] # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. select = ["ALL"] # ICN001 not adhering to numpy as np convention due to # name collision, where np is used as num params # N801 CamelCase is not always appropriate # PLR0913 number of arguments sometimes needs to be > 5 # FBT001 and FBT002, positional boolean arguments are allowed! # PT011, I disagree about checking error messages as well # as exception types as it doubles the "cost" of fixing typos ignore = [ "COM812", "EXE002", "FA100", "E501", "D", "N801", "ICN001", "PLR0913", "FBT001", "FBT002", "PT011", "S311", # Allow use of random "PLR2004", # Magic number usage where appropriate "PLC0415", # Allow delayed imports "A002", # Allow shadowing builtins for domain field names (e.g. license) ] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [lint.per-file-ignores] "tests/**/*.py" = [ "S101", # asserts allowed in tests... "INP001", # __init__.py files are not required "ANN", "N802", "N803", "S608", # sql injection unlikely ] [format] # Like Black, use double quotes for strings. quote-style = "double" # Like Black, indent with spaces, rather than tabs. indent-style = "space" # Like Black, respect magic trailing commas. skip-magic-trailing-comma = false # Like Black, automatically detect the appropriate line ending. line-ending = "lf" docstring-code-format = true # Set the line length limit used when formatting code snippets in # docstrings. # # This only has an effect when the `docstring-code-format` setting is # enabled. docstring-code-line-length = "dynamic" cogent3-citeable-7188740/src/000077500000000000000000000000001515465136300156265ustar00rootroot00000000000000cogent3-citeable-7188740/src/citeable/000077500000000000000000000000001515465136300173765ustar00rootroot00000000000000cogent3-citeable-7188740/src/citeable/__init__.py000066400000000000000000000014611515465136300215110ustar00rootroot00000000000000"""citeable — structured BibTeX citations for cogent3 plugins.""" from importlib import metadata as _metadata from citeable._entries import ( Article, Book, Citation, CitationBase, InProceedings, Misc, Software, TechReport, Thesis, ) from citeable._json import from_jsons, load_json, to_jsons, write_json from citeable._keys import assign_unique_keys, write_bibtex from citeable._parser import from_bibtex_string __version__ = _metadata.version("citeable") __all__ = [ "Article", "Book", "Citation", "CitationBase", "InProceedings", "Misc", "Software", "TechReport", "Thesis", "__version__", "assign_unique_keys", "from_bibtex_string", "from_jsons", "load_json", "to_jsons", "write_bibtex", "write_json", ] cogent3-citeable-7188740/src/citeable/_entries.py000066400000000000000000000444771515465136300216000ustar00rootroot00000000000000"""Citation entry type classes for all supported BibTeX types.""" from __future__ import annotations from abc import ABC, abstractmethod from citeable._keys import generate_key from citeable._validate import extract_surname, require_field, require_non_empty_authors def _format_bibtex_field(name: str, value: str) -> str: return f" {name:<10}= {{{value}}}," def _author_str(authors: list[str]) -> str: return " and ".join(authors) def _author_summary(authors: list[str]) -> str: surname = extract_surname(authors[0]) return f"{surname} et al." if len(authors) > 1 else surname def _title_excerpt(title: str, max_len: int = 50) -> str: return title if len(title) <= max_len else title[:max_len] + "\u2026" def _content_fields(obj: object, exclude: set[str]) -> tuple[object, ...]: """Return a tuple of content field values for equality/hashing. Lists are converted to tuples so the result is hashable. """ vals: list[object] = [] vals.extend( tuple(v) if isinstance(v, list) else v for k, v in sorted(vars(obj).items()) if k not in exclude ) return tuple(vals) _EXCLUDED: set[str] = {"key", "app"} class CitationBase(ABC): """Abstract base class for all citation entry types.""" author: list[str] title: str year: int doi: str | None url: str | None note: str | None key: str app: str | None def _init_base( self, author: list[str], title: str, year: int, *, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: """Set common fields shared by all citation types.""" require_non_empty_authors(author, type(self).__name__) self.author = author self.title = title self.year = year self.doi = doi self.url = url self.note = note self.key = key if key is not None else generate_key(author, year) self.app = app def __eq__(self, other: object) -> bool: if self is other: return True if type(self) is not type(other): return NotImplemented return _content_fields(self, _EXCLUDED) == _content_fields(other, _EXCLUDED) def __hash__(self) -> int: return hash((type(self).__name__, _content_fields(self, _EXCLUDED))) def summary(self) -> tuple[str, str]: """Return ``(app_name, citation_string)``.""" app_name = self.app if self.app is not None else "" auth = _author_summary(self.author) excerpt = _title_excerpt(self.title) return (app_name, f"{auth} {self.year} {excerpt}") def __repr__(self) -> str: fields = self._repr_fields() auto_key = generate_key(self.author, self.year) if self.key != auto_key: fields.insert(0, ("key", self.key)) parts = [f" {name}={value!r}," for name, value in fields] body = "\n".join(parts) return f"{type(self).__name__}(\n{body}\n)" def _append_common_bibtex(self, lines: list[str]) -> None: """Append doi/url/note BibTeX fields if set.""" if self.doi is not None: lines.append(_format_bibtex_field("doi", self.doi)) if self.url is not None: lines.append(_format_bibtex_field("url", self.url)) if self.note is not None: lines.append(_format_bibtex_field("note", self.note)) def _append_common_optional_repr(self, fields: list[tuple[str, object]]) -> None: """Append doi/url/note to repr field list if set.""" if self.doi is not None: fields.append(("doi", self.doi)) if self.url is not None: fields.append(("url", self.url)) if self.note is not None: fields.append(("note", self.note)) def to_dict(self) -> dict[str, object]: """Return a JSON-serialisable dict including a ``"type"`` discriminator.""" return {"type": type(self).__name__, **vars(self)} @classmethod def from_dict(cls, data: dict[str, object]) -> CitationBase: """Reconstruct a citation from a dict produced by :meth:`to_dict`. Raises ``ValueError`` if the ``"type"`` key is missing or unknown. """ data = dict(data) # shallow copy so we don't mutate the caller's dict type_name = data.pop("type", None) if type_name is None: msg = "dict is missing required 'type' key" raise ValueError(msg) entry_cls = _ENTRY_TYPES.get(str(type_name)) if entry_cls is None: msg = f"unknown citation type {type_name!r}" raise ValueError(msg) return entry_cls(**data) @abstractmethod def _repr_fields(self) -> list[tuple[str, object]]: """Return the list of ``(name, value)`` pairs for ``__repr__``.""" # Keep Citation as a public alias for the base class. Citation = CitationBase class Article(CitationBase): """An ``@article`` BibTeX entry.""" journal: str volume: int | None pages: str | None article_number: str | None number: int | None def __init__( self, author: list[str], title: str, year: int, journal: str, *, volume: int | None = None, pages: str | None = None, article_number: str | None = None, number: int | None = None, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: require_field(journal, "journal", "Article") self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) self.journal = journal self.volume = volume self.pages = pages self.article_number = article_number self.number = number def __str__(self) -> str: lines = [ f"@article{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("journal", self.journal), _format_bibtex_field("year", str(self.year)), ] if self.volume is not None: lines.append(_format_bibtex_field("volume", str(self.volume))) if self.number is not None: lines.append(_format_bibtex_field("number", str(self.number))) if self.pages is not None: lines.append(_format_bibtex_field("pages", self.pages)) if self.article_number is not None: lines.append(_format_bibtex_field("article_number", self.article_number)) self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ("journal", self.journal), ] if self.volume is not None: fields.append(("volume", self.volume)) if self.pages is not None: fields.append(("pages", self.pages)) if self.article_number is not None: fields.append(("article_number", self.article_number)) if self.number is not None: fields.append(("number", self.number)) self._append_common_optional_repr(fields) return fields class Book(CitationBase): """A ``@book`` BibTeX entry.""" publisher: str edition: str | None editor: list[str] | None def __init__( self, author: list[str], title: str, year: int, publisher: str, *, edition: str | None = None, editor: list[str] | None = None, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: require_field(publisher, "publisher", "Book") self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) self.publisher = publisher self.edition = edition self.editor = editor def __str__(self) -> str: lines = [ f"@book{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("publisher", self.publisher), _format_bibtex_field("year", str(self.year)), ] if self.edition is not None: lines.append(_format_bibtex_field("edition", self.edition)) if self.editor is not None: lines.append(_format_bibtex_field("editor", _author_str(self.editor))) self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ("publisher", self.publisher), ] if self.edition is not None: fields.append(("edition", self.edition)) if self.editor is not None: fields.append(("editor", self.editor)) self._append_common_optional_repr(fields) return fields class InProceedings(CitationBase): """An ``@inproceedings`` BibTeX entry.""" booktitle: str pages: str | None publisher: str | None editor: list[str] | None def __init__( self, author: list[str], title: str, year: int, booktitle: str, *, pages: str | None = None, publisher: str | None = None, editor: list[str] | None = None, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: require_field(booktitle, "booktitle", "InProceedings") self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) self.booktitle = booktitle self.pages = pages self.publisher = publisher self.editor = editor def __str__(self) -> str: lines = [ f"@inproceedings{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("booktitle", self.booktitle), _format_bibtex_field("year", str(self.year)), ] if self.pages is not None: lines.append(_format_bibtex_field("pages", self.pages)) if self.publisher is not None: lines.append(_format_bibtex_field("publisher", self.publisher)) if self.editor is not None: lines.append(_format_bibtex_field("editor", _author_str(self.editor))) self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ("booktitle", self.booktitle), ] if self.pages is not None: fields.append(("pages", self.pages)) if self.publisher is not None: fields.append(("publisher", self.publisher)) if self.editor is not None: fields.append(("editor", self.editor)) self._append_common_optional_repr(fields) return fields class TechReport(CitationBase): """A ``@techreport`` BibTeX entry.""" institution: str number: str | None def __init__( self, author: list[str], title: str, year: int, institution: str, *, number: str | None = None, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: require_field(institution, "institution", "TechReport") self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) self.institution = institution self.number = number def __str__(self) -> str: lines = [ f"@techreport{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("institution", self.institution), _format_bibtex_field("year", str(self.year)), ] if self.number is not None: lines.append(_format_bibtex_field("number", self.number)) self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ("institution", self.institution), ] if self.number is not None: fields.append(("number", self.number)) self._append_common_optional_repr(fields) return fields class Thesis(CitationBase): """A ``@phdthesis`` or ``@mastersthesis`` BibTeX entry.""" school: str thesis_type: str def __init__( self, author: list[str], title: str, year: int, school: str, thesis_type: str, *, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: require_field(school, "school", "Thesis") require_field(thesis_type, "thesis_type", "Thesis") if thesis_type not in ("phd", "masters"): msg = f"Thesis thesis_type must be 'phd' or 'masters'; got {thesis_type!r}" raise ValueError(msg) self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) self.school = school self.thesis_type = thesis_type def __str__(self) -> str: bib_type = "phdthesis" if self.thesis_type == "phd" else "mastersthesis" lines = [ f"@{bib_type}{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("school", self.school), _format_bibtex_field("year", str(self.year)), ] self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ("school", self.school), ("thesis_type", self.thesis_type), ] self._append_common_optional_repr(fields) return fields class Software(CitationBase): """A ``@software`` BibTeX entry.""" publisher: str | None version: str | None license: str | None def __init__( self, author: list[str], title: str, year: int, *, publisher: str | None = None, version: str | None = None, license: str | None = None, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) self.publisher = publisher self.version = version self.license = license def __str__(self) -> str: lines = [ f"@software{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("year", str(self.year)), ] if self.publisher is not None: lines.append(_format_bibtex_field("publisher", self.publisher)) if self.version is not None: lines.append(_format_bibtex_field("version", self.version)) if self.license is not None: lines.append(_format_bibtex_field("license", self.license)) self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ] if self.publisher is not None: fields.append(("publisher", self.publisher)) if self.version is not None: fields.append(("version", self.version)) if self.license is not None: fields.append(("license", self.license)) self._append_common_optional_repr(fields) return fields class Misc(CitationBase): """A ``@misc`` BibTeX entry.""" def __init__( self, author: list[str], title: str, year: int, *, doi: str | None = None, url: str | None = None, note: str | None = None, key: str | None = None, app: str | None = None, ) -> None: self._init_base( author, title, year, doi=doi, url=url, note=note, key=key, app=app ) def __str__(self) -> str: lines = [ f"@misc{{{self.key},", _format_bibtex_field("author", _author_str(self.author)), _format_bibtex_field("title", self.title), _format_bibtex_field("year", str(self.year)), ] self._append_common_bibtex(lines) lines.append("}") return "\n".join(lines) def _repr_fields(self) -> list[tuple[str, object]]: fields: list[tuple[str, object]] = [ ("author", self.author), ("title", self.title), ("year", self.year), ] self._append_common_optional_repr(fields) return fields _ENTRY_TYPES: dict[str, type[CitationBase]] = { "Article": Article, "Book": Book, "InProceedings": InProceedings, "TechReport": TechReport, "Thesis": Thesis, "Software": Software, "Misc": Misc, } cogent3-citeable-7188740/src/citeable/_json.py000066400000000000000000000017221515465136300210620ustar00rootroot00000000000000"""JSON serialisation helpers for citation collections.""" from __future__ import annotations import json import pathlib from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Iterable from citeable._entries import CitationBase def to_jsons(citations: Iterable[CitationBase]) -> str: """Return a JSON string from an iterable of citations.""" return json.dumps([c.to_dict() for c in citations]) def from_jsons(data: str) -> list[CitationBase]: """Return a list of citations from a JSON string.""" return [CitationBase.from_dict(d) for d in json.loads(data)] def write_json(*, citations: Iterable[CitationBase], path: str | pathlib.Path) -> None: """Write citations to a JSON file at *path*.""" pathlib.Path(path).write_text(to_jsons(citations)) def load_json(path: str | pathlib.Path) -> list[CitationBase]: """Read citations from a JSON file at *path*.""" return from_jsons(pathlib.Path(path).read_text()) cogent3-citeable-7188740/src/citeable/_keys.py000066400000000000000000000035131515465136300210640ustar00rootroot00000000000000"""Auto key generation, unique key assignment, and .bib file writing.""" from __future__ import annotations import string from pathlib import Path from typing import TYPE_CHECKING from citeable._validate import extract_surname if TYPE_CHECKING: from citeable._entries import CitationBase def generate_key(author: list[str], year: int) -> str: """Generate a citation key from the first author surname and year. Algorithm: 1. Extract surname from the first author (before comma, or last token). 2. Strip non-ASCII and spaces; preserve original case. 3. Return ``"{surname}.{year}"``. """ surname = extract_surname(author[0]) return f"{surname}.{year}" def assign_unique_keys(citations: list[CitationBase]) -> list[CitationBase]: """Deduplicate *citations* by value and resolve key collisions in-place. Returns the deduplicated list (mutated in-place for surviving objects). """ seen: dict[CitationBase, CitationBase] = {} unique: list[CitationBase] = [] for c in citations: if c not in seen: seen[c] = c unique.append(c) key_groups: dict[str, list[CitationBase]] = {} for c in unique: key_groups.setdefault(c.key, []).append(c) for key, group in key_groups.items(): if len(group) <= 1: continue if len(group) > 26: msg = f"More than 26 collisions for key {key!r}" raise ValueError(msg) for i, c in enumerate(group): c.key = f"{key}.{string.ascii_lowercase[i]}" return unique def write_bibtex(citations: list[CitationBase], path: str | Path) -> None: """Deduplicate, assign unique keys, and write a ``.bib`` file.""" unique = assign_unique_keys(citations) content = "\n\n".join(str(c) for c in unique) Path(path).write_text(content, encoding="utf-8") cogent3-citeable-7188740/src/citeable/_parser.py000066400000000000000000000152221515465136300214050ustar00rootroot00000000000000"""BibTeX string parser — single-record ``from_bibtex_string``.""" from __future__ import annotations import re from collections.abc import Callable from typing import Any from citeable._entries import ( Article, Book, CitationBase, InProceedings, Misc, Software, TechReport, Thesis, ) _ENTRY_START_RE = re.compile(r"@(\w+)\s*\{") _TYPE_MAP: dict[str, type[CitationBase]] = { "article": Article, "book": Book, "inproceedings": InProceedings, "techreport": TechReport, "phdthesis": Thesis, "mastersthesis": Thesis, "software": Software, "misc": Misc, } def _extract_entries(bibtex: str) -> list[tuple[str, str, str]]: """Return list of ``(entry_type, cite_key, body)`` tuples.""" results: list[tuple[str, str, str]] = [] for m in _ENTRY_START_RE.finditer(bibtex): entry_type = m.group(1) start = m.end() depth = 1 pos = start while pos < len(bibtex) and depth > 0: if bibtex[pos] == "{": depth += 1 elif bibtex[pos] == "}": depth -= 1 pos += 1 body = bibtex[start : pos - 1] comma_idx = body.find(",") if comma_idx == -1: cite_key = body.strip() fields_body = "" else: cite_key = body[:comma_idx].strip() fields_body = body[comma_idx + 1 :] results.append((entry_type, cite_key, fields_body)) return results def _normalise_author(name: str) -> str: """Normalise an author name to ``"Last, First"`` format.""" name = name.strip() if "," in name: return name parts = name.split() return name if len(parts) <= 1 else f"{parts[-1]}, {' '.join(parts[:-1])}" def _parse_authors(raw: str) -> list[str]: return [_normalise_author(a) for a in raw.split(" and ")] _FIELD_START_RE = re.compile(r"^\s*(\w+)\s*=\s*", re.MULTILINE) def _extract_value(raw: str) -> str: """Extract a field value from the text after ``=``. Handles brace-delimited (with nesting), quoted, and bare values. """ raw = raw.strip() if raw.startswith("{"): depth = 0 for i, ch in enumerate(raw): if ch == "{": depth += 1 elif ch == "}": depth -= 1 if depth == 0: return raw[1:i] elif raw.startswith('"'): end = raw.index('"', 1) return raw[1:end] else: return raw.rstrip(",").strip() return raw def _parse_fields(body: str) -> dict[str, str]: """Parse BibTeX fields supporting braced, quoted, and bare values.""" matches = list(_FIELD_START_RE.finditer(body)) fields: dict[str, str] = {} for i, m in enumerate(matches): name = m.group(1).lower() val_start = m.end() val_end = matches[i + 1].start() if i + 1 < len(matches) else len(body) fields[name] = _extract_value(body[val_start:val_end]) return fields def _common_kwargs( fields: dict[str, str], cite_key: str, ) -> dict[str, Any]: kwargs: dict[str, Any] = {"key": cite_key} if "author" in fields: kwargs["author"] = _parse_authors(fields["author"]) if "title" in fields: kwargs["title"] = fields["title"] if "year" in fields: kwargs["year"] = int(fields["year"]) for name in ("doi", "url", "note"): if name in fields: kwargs[name] = fields[name] return kwargs def _article_kwargs(fields: dict[str, str]) -> dict[str, Any]: kwargs: dict[str, Any] = {} if "journal" in fields: kwargs["journal"] = fields["journal"] if "volume" in fields: kwargs["volume"] = int(fields["volume"]) if "pages" in fields: kwargs["pages"] = fields["pages"] if "article_number" in fields: kwargs["article_number"] = fields["article_number"] if "number" in fields: kwargs["number"] = int(fields["number"]) return kwargs def _book_kwargs(fields: dict[str, str]) -> dict[str, Any]: kwargs: dict[str, Any] = {} if "publisher" in fields: kwargs["publisher"] = fields["publisher"] if "edition" in fields: kwargs["edition"] = fields["edition"] if "editor" in fields: kwargs["editor"] = _parse_authors(fields["editor"]) return kwargs def _inproceedings_kwargs(fields: dict[str, str]) -> dict[str, Any]: kwargs: dict[str, Any] = {} if "booktitle" in fields: kwargs["booktitle"] = fields["booktitle"] if "pages" in fields: kwargs["pages"] = fields["pages"] if "publisher" in fields: kwargs["publisher"] = fields["publisher"] if "editor" in fields: kwargs["editor"] = _parse_authors(fields["editor"]) return kwargs def _techreport_kwargs(fields: dict[str, str]) -> dict[str, Any]: kwargs: dict[str, Any] = {} if "institution" in fields: kwargs["institution"] = fields["institution"] if "number" in fields: kwargs["number"] = fields["number"] return kwargs def _thesis_kwargs( fields: dict[str, str], entry_type: str, ) -> dict[str, Any]: kwargs: dict[str, Any] = {} if "school" in fields: kwargs["school"] = fields["school"] kwargs["thesis_type"] = "phd" if entry_type == "phdthesis" else "masters" return kwargs def _software_kwargs(fields: dict[str, str]) -> dict[str, Any]: kwargs: dict[str, Any] = { name: fields[name] for name in ("publisher", "version", "license") if name in fields } return kwargs _KwargsFunc = Callable[[dict[str, str]], dict[str, Any]] _TYPE_KWARGS: dict[type[CitationBase], _KwargsFunc] = { Article: _article_kwargs, Book: _book_kwargs, InProceedings: _inproceedings_kwargs, TechReport: _techreport_kwargs, Software: _software_kwargs, } def from_bibtex_string(bibtex: str) -> CitationBase: """Parse a single BibTeX record and return the corresponding object.""" entries = _extract_entries(bibtex) if not entries: msg = "No BibTeX entry found in input string" raise ValueError(msg) if len(entries) > 1: msg = "Multiple BibTeX entries found; only single-record strings are supported" raise ValueError(msg) entry_type_raw, cite_key, body = entries[0] entry_type = entry_type_raw.lower() if entry_type not in _TYPE_MAP: msg = f"Unsupported BibTeX entry type: @{entry_type}" raise ValueError(msg) cls = _TYPE_MAP[entry_type] fields = _parse_fields(body) kwargs = _common_kwargs(fields, cite_key) if cls is Thesis: kwargs.update(_thesis_kwargs(fields, entry_type)) elif cls in _TYPE_KWARGS: kwargs.update(_TYPE_KWARGS[cls](fields)) return cls(**kwargs) cogent3-citeable-7188740/src/citeable/_validate.py000066400000000000000000000020571515465136300217040ustar00rootroot00000000000000"""Shared validation logic for citeable entry types.""" from __future__ import annotations import re def require_field( value: object, field_name: str, entry_type: str, ) -> None: """Raise ``ValueError`` if *value* is ``None``.""" if value is None: msg = f"{entry_type} requires {field_name!r}; received None" raise ValueError(msg) def require_non_empty_authors(authors: list[str], entry_type: str) -> None: """Raise ``ValueError`` if *authors* is empty.""" if not authors: msg = f"{entry_type} requires at least one author" raise ValueError(msg) def extract_surname(name: str) -> str: """Extract surname from an author name string. Handles both ``"Last, First"`` and ``"First Last"`` formats. Returns the surname stripped of non-ASCII characters, preserving original case. """ if "," in name: surname = name.split(",", maxsplit=1)[0].strip() else: surname = name.rsplit(maxsplit=1)[-1].strip() surname = re.sub(r"[^A-Za-z]", "", surname) return surname cogent3-citeable-7188740/src/citeable/py.typed000066400000000000000000000000001515465136300210630ustar00rootroot00000000000000cogent3-citeable-7188740/tests/000077500000000000000000000000001515465136300162015ustar00rootroot00000000000000cogent3-citeable-7188740/tests/test_entries.py000066400000000000000000000244751515465136300212770ustar00rootroot00000000000000"""Tests for citation entry type construction and validation.""" import pytest from citeable import ( Article, Book, InProceedings, Misc, Software, TechReport, Thesis, ) # ── Article ────────────────────────────────────────────────────────────── def test_article_construction(): a = Article( author=["Huttley, Gavin", "Caley, Katherine"], title="A paper", year=2025, journal="JOSS", volume=10, pages="7765", ) assert a.author == ["Huttley, Gavin", "Caley, Katherine"] assert a.journal == "JOSS" assert a.volume == 10 assert a.pages == "7765" assert a.key == "Huttley.2025" def test_article_with_article_number(): a = Article( author=["Smith, Jane"], title="Something", year=2024, journal="Nature", volume=1, article_number="e123", ) assert a.article_number == "e123" assert a.pages is None def test_article_advance_access(): a = Article( author=["Smith, Jane"], title="Something", year=2024, journal="Nature", doi="10.1234/example", ) assert a.volume is None assert a.pages is None assert a.article_number is None def test_article_missing_journal(): with pytest.raises(ValueError, match="Article requires 'journal'"): Article( author=["Smith, Jane"], title="Something", year=2024, journal=None, # type: ignore[arg-type] volume=1, pages="1-10", ) def test_article_empty_authors(): with pytest.raises(ValueError, match="at least one author"): Article( author=[], title="Something", year=2024, journal="Nature", volume=1, pages="1-10", ) # ── Book ───────────────────────────────────────────────────────────────── def test_book_construction(): b = Book( author=["Knuth, Donald"], title="The Art of Programming", year=1997, publisher="Addison-Wesley", ) assert b.publisher == "Addison-Wesley" assert b.key == "Knuth.1997" def test_book_missing_publisher(): with pytest.raises(ValueError, match="Book requires 'publisher'"): Book( author=["Knuth, Donald"], title="The Art", year=1997, publisher=None, # type: ignore[arg-type] ) # ── InProceedings ──────────────────────────────────────────────────────── def test_inproceedings_construction(): ip = InProceedings( author=["Doe, John"], title="A Conference Paper", year=2023, booktitle="Proceedings of Foo", ) assert ip.booktitle == "Proceedings of Foo" assert ip.key == "Doe.2023" def test_inproceedings_missing_booktitle(): with pytest.raises(ValueError, match="InProceedings requires 'booktitle'"): InProceedings( author=["Doe, John"], title="Paper", year=2023, booktitle=None, # type: ignore[arg-type] ) # ── TechReport ─────────────────────────────────────────────────────────── def test_techreport_construction(): tr = TechReport( author=["Turing, Alan"], title="On Computable Numbers", year=1936, institution="Cambridge", ) assert tr.institution == "Cambridge" assert tr.key == "Turing.1936" def test_techreport_missing_institution(): with pytest.raises(ValueError, match="TechReport requires 'institution'"): TechReport( author=["Turing, Alan"], title="A Report", year=1936, institution=None, # type: ignore[arg-type] ) # ── Thesis ─────────────────────────────────────────────────────────────── def test_thesis_phd(): t = Thesis( author=["Student, Alice"], title="My Thesis", year=2022, school="MIT", thesis_type="phd", ) assert t.thesis_type == "phd" assert t.school == "MIT" def test_thesis_masters(): t = Thesis( author=["Student, Bob"], title="My Thesis", year=2021, school="Oxford", thesis_type="masters", ) assert t.thesis_type == "masters" def test_thesis_invalid_type(): with pytest.raises(ValueError, match="'phd' or 'masters'"): Thesis( author=["Student, Alice"], title="My Thesis", year=2022, school="MIT", thesis_type="bachelor", ) def test_thesis_missing_school(): with pytest.raises(ValueError, match="Thesis requires 'school'"): Thesis( author=["Student, Alice"], title="My Thesis", year=2022, school=None, # type: ignore[arg-type] thesis_type="phd", ) # ── Software ───────────────────────────────────────────────────────────── def test_software_construction(): s = Software( author=["Dev, Jane"], title="my-tool", year=2024, version="1.0.0", url="https://github.com/example", ) assert s.version == "1.0.0" assert s.key == "Dev.2024" def test_software_minimal(): s = Software( author=["Dev, Jane"], title="my-tool", year=2024, ) assert s.version is None assert s.publisher is None # ── Misc ───────────────────────────────────────────────────────────────── def test_misc_construction(): m = Misc( author=["Author, Some"], title="A Misc Entry", year=2020, note="Some note", ) assert m.note == "Some note" assert m.key == "Author.2020" # ── Equality and hashing ──────────────────────────────────────────────── def test_equality_same_content(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) b = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) assert a == b assert hash(a) == hash(b) def test_equality_excludes_key_and_app(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", key="custom.key", app="app1", ) b = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", key="other.key", app="app2", ) assert a == b assert hash(a) == hash(b) def test_identity_equality(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) b = a assert a is b assert a == b def test_inequality_different_content(): a = Article( author=["Smith, A"], title="Paper 1", year=2024, journal="J", volume=1, pages="1", ) b = Article( author=["Smith, A"], title="Paper 2", year=2024, journal="J", volume=1, pages="1", ) assert a != b def test_inequality_different_types(): a = Misc(author=["Smith, A"], title="Thing", year=2024) b = Software(author=["Smith, A"], title="Thing", year=2024) assert a != b def test_set_deduplication(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) b = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", key="different", ) assert len({a, b}) == 1 def test_author_order_matters(): a = Article( author=["Smith, A", "Jones, B"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) b = Article( author=["Jones, B", "Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) assert a != b # ── key auto-generation ───────────────────────────────────────────────── def test_key_auto_generated(): a = Article( author=["Huttley, Gavin"], title="Paper", year=2025, journal="J", volume=1, pages="1", ) assert a.key == "Huttley.2025" def test_key_explicit(): a = Article( author=["Huttley, Gavin"], title="Paper", year=2025, journal="J", volume=1, pages="1", key="custom", ) assert a.key == "custom" def test_key_from_first_last_format(): a = Misc(author=["Gavin Huttley"], title="Paper", year=2025) assert a.key == "Huttley.2025" # ── app field ──────────────────────────────────────────────────────────── def test_app_field(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", app="my-plugin", ) assert a.app == "my-plugin" def test_app_default_none(): a = Misc(author=["Smith, A"], title="Paper", year=2024) assert a.app is None cogent3-citeable-7188740/tests/test_keys.py000066400000000000000000000073221515465136300205710ustar00rootroot00000000000000"""Tests for key generation and assign_unique_keys.""" import pytest from citeable import Article, Misc, Software, assign_unique_keys from citeable._keys import generate_key from citeable._validate import extract_surname # ── generate_key ───────────────────────────────────────────────────────── def test_generate_key_comma_format(): assert generate_key(["Huttley, Gavin"], 2025) == "Huttley.2025" def test_generate_key_space_format(): assert generate_key(["Gavin Huttley"], 2025) == "Huttley.2025" def test_generate_key_strips_non_ascii(): assert generate_key(["Müller, Hans"], 2020) == "Mller.2020" def test_generate_key_preserves_case(): assert generate_key(["van der berg, Jan"], 2020) == "vanderberg.2020" def test_generate_key_camel_case_preserved(): assert generate_key(["McArthur, Robert"], 2021) == "McArthur.2021" assert generate_key(["McDonald, Daniel"], 2022) == "McDonald.2022" def test_extract_surname_preserves_case(): assert extract_surname("McArthur, Robert") == "McArthur" assert extract_surname("McDonald, Daniel") == "McDonald" # ── assign_unique_keys ────────────────────────────────────────────────── def test_assign_unique_keys_no_collision(): a = Misc(author=["Smith, A"], title="Paper A", year=2024) b = Misc(author=["Jones, B"], title="Paper B", year=2024) result = assign_unique_keys([a, b]) assert len(result) == 2 assert a.key == "Smith.2024" assert b.key == "Jones.2024" def test_assign_unique_keys_collision(): a = Misc(author=["Smith, A"], title="Paper A", year=2024) b = Misc(author=["Smith, B"], title="Paper B", year=2024) result = assign_unique_keys([a, b]) assert len(result) == 2 assert a.key == "Smith.2024.a" assert b.key == "Smith.2024.b" def test_assign_unique_keys_deduplication(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", app="app1", ) b = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", app="app2", ) result = assign_unique_keys([a, b]) assert len(result) == 1 assert result[0] is a def test_assign_unique_keys_preserves_order(): a = Misc(author=["Smith, A"], title="First", year=2024) b = Misc(author=["Smith, B"], title="Second", year=2024) c = Misc(author=["Smith, C"], title="Third", year=2024) result = assign_unique_keys([a, b, c]) assert result == [a, b, c] assert a.key == "Smith.2024.a" assert b.key == "Smith.2024.b" assert c.key == "Smith.2024.c" def test_assign_unique_keys_returns_list(): a = Misc(author=["Smith, A"], title="Paper", year=2024) result = assign_unique_keys([a]) assert isinstance(result, list) assert result == [a] def test_assign_unique_keys_empty(): result = assign_unique_keys([]) assert result == [] def test_assign_unique_keys_too_many_collisions(): citations = [ Misc(author=["Smith, A"], title=f"Paper {i}", year=2024) for i in range(27) ] with pytest.raises(ValueError, match="More than 26 collisions"): assign_unique_keys(citations) def test_assign_unique_keys_mixed_types_same_key(): a = Misc(author=["Smith, A"], title="Misc Paper", year=2024) b = Software(author=["Smith, B"], title="Software", year=2024) result = assign_unique_keys([a, b]) assert len(result) == 2 assert a.key == "Smith.2024.a" assert b.key == "Smith.2024.b" cogent3-citeable-7188740/tests/test_parser.py000066400000000000000000000145331515465136300211140ustar00rootroot00000000000000"""Tests for from_bibtex_string parser.""" import pytest from citeable import ( Article, Book, InProceedings, Misc, Software, TechReport, Thesis, from_bibtex_string, ) def test_parse_article(): cite = from_bibtex_string(""" @article{Huttley.2025, doi = {10.21105/joss.07765}, url = {https://doi.org/10.21105/joss.07765}, year = {2025}, volume = {10}, number = {110}, pages = {7765}, author = {Huttley, Gavin and Caley, Katherine and McArthur, Robert}, title = {diverse-seq}, journal = {Journal of Open Source Software}, } """) assert isinstance(cite, Article) assert cite.key == "Huttley.2025" assert cite.author == ["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"] assert cite.journal == "Journal of Open Source Software" assert cite.volume == 10 assert cite.number == 110 assert cite.pages == "7765" assert cite.doi == "10.21105/joss.07765" def test_parse_software_1(): cite = from_bibtex_string("""@software{cogent3, title = {{cogent3}: making sense of sequence}, author = {Huttley, Gavin and Caley, Katherine and Fotovat, Nabi and Ma, Stephen Ka-Wah and Koh, Moses and Morris, Richard and McArthur, Robert and McDonald, Daniel and Jaya, Fred and Maxwell, Peter and Martini, James and La, Thomas and Lang, Yapeng}, year = 2025, month = jul, doi = {10.5281/zenodo.16519079}, urldate = {2025-08-02}, howpublished = {Zenodo} } """) assert isinstance(cite, Software) def test_parse_book(): cite = from_bibtex_string(""" @book{Knuth.1997, author = {Knuth, Donald}, title = {The Art of Computer Programming}, publisher = {Addison-Wesley}, year = {1997}, edition = {3rd}, } """) assert isinstance(cite, Book) assert cite.publisher == "Addison-Wesley" assert cite.edition == "3rd" def test_parse_inproceedings(): cite = from_bibtex_string(""" @inproceedings{Doe.2023, author = {Doe, John}, title = {A Paper}, booktitle = {Proceedings of Foo}, year = {2023}, pages = {1--10}, } """) assert isinstance(cite, InProceedings) assert cite.booktitle == "Proceedings of Foo" assert cite.pages == "1--10" def test_parse_techreport(): cite = from_bibtex_string(""" @techreport{Turing.1936, author = {Turing, Alan}, title = {On Computable Numbers}, institution = {Cambridge}, year = {1936}, number = {TR-42}, } """) assert isinstance(cite, TechReport) assert cite.institution == "Cambridge" assert cite.number == "TR-42" def test_parse_phdthesis(): cite = from_bibtex_string(""" @phdthesis{Student.2022, author = {Student, Alice}, title = {My Thesis}, school = {MIT}, year = {2022}, } """) assert isinstance(cite, Thesis) assert cite.thesis_type == "phd" assert cite.school == "MIT" def test_parse_mastersthesis(): cite = from_bibtex_string(""" @mastersthesis{Student.2021, author = {Student, Bob}, title = {My Thesis}, school = {Oxford}, year = {2021}, } """) assert isinstance(cite, Thesis) assert cite.thesis_type == "masters" def test_parse_software(): cite = from_bibtex_string(""" @software{Dev.2024, author = {Dev, Jane}, title = {my-tool}, year = {2024}, version = {1.0.0}, url = {https://github.com/example}, } """) assert isinstance(cite, Software) assert cite.version == "1.0.0" def test_parse_misc(): cite = from_bibtex_string(""" @misc{Author.2020, author = {Author, Some}, title = {A Misc Entry}, year = {2020}, note = {Some note}, } """) assert isinstance(cite, Misc) assert cite.note == "Some note" def test_parse_author_normalisation(): cite = from_bibtex_string(""" @misc{Smith.2024, author = {John Smith and Jane Doe}, title = {A Paper}, year = {2024}, } """) assert cite.author == ["Smith, John", "Doe, Jane"] def test_parse_single_name_author(): """Single-name authors (e.g. mononyms) are kept as-is.""" cite = from_bibtex_string(""" @misc{Aristotle.2024, author = {Aristotle}, title = {Metaphysics}, year = {2024}, } """) assert cite.author == ["Aristotle"] def test_parse_single_name_among_multiple_authors(): cite = from_bibtex_string(""" @misc{Plato.2024, author = {Plato and Jane Smith}, title = {Dialogues}, year = {2024}, } """) assert cite.author == ["Plato", "Smith, Jane"] def test_parse_preserves_cite_key(): cite = from_bibtex_string(""" @misc{my_custom_key, author = {Smith, John}, title = {A Paper}, year = {2024}, } """) assert cite.key == "my_custom_key" def test_parse_article_with_article_number(): cite = from_bibtex_string(""" @article{Smith.2024, author = {Smith, John}, title = {A Paper}, journal = {Nature}, year = {2024}, volume = {1}, article_number = {e42}, } """) assert isinstance(cite, Article) assert cite.article_number == "e42" def test_parse_book_with_editor(): cite = from_bibtex_string(""" @book{Knuth.1997, author = {Knuth, Donald}, title = {TAOCP}, publisher = {Addison-Wesley}, year = {1997}, editor = {Smith, Jane and Doe, John}, } """) assert isinstance(cite, Book) assert cite.editor == ["Smith, Jane", "Doe, John"] def test_parse_inproceedings_with_publisher_and_editor(): cite = from_bibtex_string(""" @inproceedings{Doe.2023, author = {Doe, John}, title = {A Paper}, booktitle = {Proceedings of Foo}, year = {2023}, publisher = {ACM}, editor = {Chair, Ed}, } """) assert isinstance(cite, InProceedings) assert cite.publisher == "ACM" assert cite.editor == ["Chair, Ed"] def test_parse_unsupported_type(): with pytest.raises(ValueError, match="Unsupported BibTeX entry type"): from_bibtex_string(""" @proceedings{Foo.2024, author = {Smith, John}, title = {A Paper}, year = {2024}, } """) def test_parse_no_entry(): with pytest.raises(ValueError, match="No BibTeX entry found"): from_bibtex_string("not a bibtex string") def test_parse_multiple_entries(): with pytest.raises(ValueError, match="Multiple BibTeX entries"): from_bibtex_string(""" @misc{A.2024, author = {A, B}, title = {First}, year = {2024}, } @misc{C.2024, author = {C, D}, title = {Second}, year = {2024}, } """) cogent3-citeable-7188740/tests/test_serialise.py000066400000000000000000000324631515465136300216020ustar00rootroot00000000000000"""Tests for __str__, __repr__, summary(), and JSON serialisation.""" import pathlib import pytest from citeable import ( Article, Book, CitationBase, InProceedings, Misc, Software, TechReport, Thesis, from_jsons, load_json, to_jsons, write_json, ) # ── __str__ (BibTeX output) ───────────────────────────────────────────── def test_article_str(): a = Article( author=["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"], title="diverse-seq", journal="Journal of Open Source Software", year=2025, volume=10, number=110, pages="7765", doi="10.21105/joss.07765", url="https://doi.org/10.21105/joss.07765", ) result = str(a) assert result.startswith("@article{Huttley.2025,") assert "author" in result assert "Huttley, Gavin and Caley, Katherine and McArthur, Robert" in result assert "journal" in result assert "volume" in result assert "number" in result assert "pages" in result assert "doi" in result assert "url" in result assert result.endswith("}") def test_thesis_str_phd(): t = Thesis( author=["Student, Alice"], title="My Thesis", year=2022, school="MIT", thesis_type="phd", ) result = str(t) assert result.startswith("@phdthesis{") def test_thesis_str_masters(): t = Thesis( author=["Student, Bob"], title="My Thesis", year=2021, school="Oxford", thesis_type="masters", ) result = str(t) assert result.startswith("@mastersthesis{") def test_software_str(): s = Software( author=["Dev, Jane"], title="my-tool", year=2024, version="1.0.0", ) result = str(s) assert result.startswith("@software{") assert "version" in result def test_article_str_with_article_number(): a = Article( author=["Smith, A"], title="Paper", journal="J", year=2024, volume=1, article_number="e123", note="A note", ) result = str(a) assert "article_number" in result assert "e123" in result assert "note" in result def test_book_str_all_optional_fields(): b = Book( author=["Knuth, Donald"], title="TAOCP", publisher="Addison-Wesley", year=1997, edition="3rd", editor=["Smith, Jane"], doi="10.1234/book", url="https://example.com", note="Classic text", ) result = str(b) assert result.startswith("@book{") assert "edition" in result assert "editor" in result assert "Smith, Jane" in result assert "doi" in result assert "url" in result assert "note" in result def test_inproceedings_str_all_optional_fields(): ip = InProceedings( author=["Doe, John"], title="A Paper", booktitle="Proceedings of Foo", year=2023, pages="1--10", publisher="ACM", editor=["Chair, Ed"], doi="10.1234/conf", url="https://example.com", note="Oral presentation", ) result = str(ip) assert result.startswith("@inproceedings{") assert "pages" in result assert "publisher" in result assert "editor" in result assert "doi" in result assert "url" in result assert "note" in result def test_techreport_str_all_optional_fields(): tr = TechReport( author=["Turing, Alan"], title="On Computable Numbers", institution="Cambridge", year=1936, number="TR-42", doi="10.1234/tr", url="https://example.com", note="Historic", ) result = str(tr) assert result.startswith("@techreport{") assert "number" in result assert "doi" in result assert "url" in result assert "note" in result def test_software_str_all_optional_fields(): s = Software( author=["Dev, Jane"], title="my-tool", year=2024, publisher="GitHub", version="1.0.0", license="MIT", doi="10.1234/sw", url="https://github.com/example", note="Beta release", ) result = str(s) assert result.startswith("@software{") assert "publisher" in result assert "version" in result assert "license" in result assert "doi" in result assert "note" in result def test_misc_str_omits_none_fields(): m = Misc( author=["Smith, A"], title="Something", year=2024, ) result = str(m) assert "doi" not in result assert "url" not in result assert "note" not in result # ── __repr__ ───────────────────────────────────────────────────────────── def test_article_repr_omits_auto_key(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) r = repr(a) assert r.startswith("Article(") assert "key=" not in r def test_article_repr_includes_explicit_key(): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", key="custom.key", ) r = repr(a) assert "key='custom.key'" in r def test_repr_round_trip(): a = Article( author=["Huttley, Gavin", "Caley, Katherine"], title="diverse-seq", journal="JOSS", year=2025, volume=10, pages="7765", doi="10.21105/joss.07765", ) r = repr(a) assert "Article(" in r assert "author=" in r assert "title=" in r assert "journal=" in r def test_article_repr_with_article_number_and_number(): a = Article( author=["Smith, A"], title="Paper", journal="J", year=2024, volume=1, article_number="e42", number=3, url="https://example.com", note="See also v2", ) r = repr(a) assert "article_number='e42'" in r assert "number=3" in r assert "url=" in r assert "note=" in r def test_book_repr_all_optional_fields(): b = Book( author=["Knuth, Donald"], title="TAOCP", publisher="Addison-Wesley", year=1997, edition="3rd", editor=["Smith, Jane"], doi="10.1234/book", ) r = repr(b) assert r.startswith("Book(") assert "edition=" in r assert "editor=" in r assert "doi=" in r def test_inproceedings_repr_all_optional_fields(): ip = InProceedings( author=["Doe, John"], title="A Paper", booktitle="Proceedings of Foo", year=2023, pages="1--10", publisher="ACM", editor=["Chair, Ed"], ) r = repr(ip) assert r.startswith("InProceedings(") assert "pages=" in r assert "publisher=" in r assert "editor=" in r def test_techreport_repr_with_number(): tr = TechReport( author=["Turing, Alan"], title="On Computable Numbers", institution="Cambridge", year=1936, number="TR-42", ) r = repr(tr) assert r.startswith("TechReport(") assert "number=" in r def test_thesis_repr_with_optional_fields(): t = Thesis( author=["Student, Alice"], title="My Thesis", year=2022, school="MIT", thesis_type="phd", doi="10.1234/thesis", url="https://example.com", note="Summa cum laude", ) r = repr(t) assert r.startswith("Thesis(") assert "doi=" in r assert "url=" in r assert "note=" in r def test_software_repr_all_optional_fields(): s = Software( author=["Dev, Jane"], title="my-tool", year=2024, publisher="GitHub", version="1.0.0", license="MIT", ) r = repr(s) assert r.startswith("Software(") assert "publisher=" in r assert "version=" in r assert "license=" in r def test_repr_omits_none_optional_fields(): m = Misc( author=["Smith, A"], title="Thing", year=2024, ) r = repr(m) assert "doi=" not in r assert "url=" not in r assert "note=" not in r # ── summary() ──────────────────────────────────────────────────────────── def test_summary_multiple_authors(): a = Article( author=["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"], title="diverse-seq: an application for alignment-free selecting and clustering biological sequences", journal="JOSS", year=2025, volume=10, pages="7765", app="diverse-seq", ) app_name, citation_str = a.summary() assert app_name == "diverse-seq" assert citation_str.startswith("Huttley et al. 2025") assert "\u2026" in citation_str def test_summary_single_author(): s = Software( author=["Smith, Jane"], title="my-cogent3-plugin", year=2024, app="my-plugin", ) app_name, citation_str = s.summary() assert app_name == "my-plugin" assert citation_str.startswith("Smith 2024") def test_summary_no_app(): m = Misc( author=["Smith, A"], title="Something", year=2024, ) app_name, _citation_str = m.summary() assert app_name == "" def test_summary_long_title_truncated(): m = Misc( author=["Smith, A"], title="A" * 60, year=2024, ) _, citation_str = m.summary() assert citation_str.endswith("\u2026") def test_summary_short_title_not_truncated(): m = Misc( author=["Smith, A"], title="Short title", year=2024, ) _, citation_str = m.summary() assert "\u2026" not in citation_str # ── JSON round-trip ────────────────────────────────────────────────────── def _make_all_types() -> list[CitationBase]: """Return one instance of each entry type with distinctive fields.""" return [ Article( author=["Huttley, Gavin"], title="diverse-seq", year=2025, journal="JOSS", volume=10, pages="7765", key="custom.key", app="diverse-seq", ), Book( author=["Knuth, Donald"], title="TAOCP", year=1997, publisher="Addison-Wesley", edition="3rd", ), InProceedings( author=["Doe, John"], title="A Paper", year=2023, booktitle="Proceedings", pages="1--10", ), TechReport( author=["Turing, Alan"], title="On Computable Numbers", year=1936, institution="Cambridge", number="TR-42", ), Thesis( author=["Student, Alice"], title="My Thesis", year=2022, school="MIT", thesis_type="phd", ), Software( author=["Dev, Jane"], title="my-tool", year=2024, version="1.0.0", ), Misc( author=["Smith, A"], title="Something", year=2024, doi="10.1234/misc", ), ] @pytest.mark.parametrize("citation", _make_all_types(), ids=lambda c: type(c).__name__) def test_to_dict_from_dict_round_trip(citation: CitationBase) -> None: d = citation.to_dict() restored = CitationBase.from_dict(d) assert restored == citation assert restored.key == citation.key assert restored.app == citation.app @pytest.mark.parametrize("citation", _make_all_types(), ids=lambda c: type(c).__name__) def test_to_dict_contains_type(citation: CitationBase) -> None: d = citation.to_dict() assert "type" in d assert d["type"] == type(citation).__name__ def test_from_dict_unknown_type() -> None: with pytest.raises(ValueError, match="unknown citation type"): CitationBase.from_dict( {"type": "Nonexistent", "author": ["A"], "title": "T", "year": 2024} ) def test_from_dict_missing_type() -> None: with pytest.raises(ValueError, match="missing required 'type' key"): CitationBase.from_dict({"author": ["A"], "title": "T", "year": 2024}) def test_to_jsons_from_jsons_mixed_types() -> None: originals = _make_all_types() json_str = to_jsons(originals) restored = from_jsons(json_str) assert len(restored) == len(originals) for orig, rest in zip(originals, restored, strict=True): assert type(rest) is type(orig) assert rest == orig assert rest.key == orig.key assert rest.app == orig.app def test_write_json_load_json(tmp_path: pathlib.Path) -> None: originals = _make_all_types() path = tmp_path / "citations.json" write_json(citations=originals, path=path) assert path.exists() restored = load_json(path) assert len(restored) == len(originals) for orig, rest in zip(originals, restored, strict=True): assert type(rest) is type(orig) assert rest == orig assert rest.key == orig.key assert rest.app == orig.app cogent3-citeable-7188740/tests/test_write.py000066400000000000000000000043771515465136300207570ustar00rootroot00000000000000"""Tests for write_bibtex.""" from pathlib import Path import pytest from citeable import Article, Misc, write_bibtex def test_write_bibtex_creates_file(tmp_path): a = Misc(author=["Smith, A"], title="Paper A", year=2024) b = Misc(author=["Jones, B"], title="Paper B", year=2024) path = tmp_path / "refs.bib" write_bibtex([a, b], path) content = path.read_text(encoding="utf-8") assert "@misc{Smith.2024," in content assert "@misc{Jones.2024," in content assert "\n\n" in content def test_write_bibtex_deduplicates(tmp_path): a = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) b = Article( author=["Smith, A"], title="Paper", year=2024, journal="J", volume=1, pages="1", ) path = tmp_path / "refs.bib" write_bibtex([a, b], path) content = path.read_text(encoding="utf-8") assert content.count("@article{") == 1 def test_write_bibtex_resolves_collisions(tmp_path): a = Misc(author=["Smith, A"], title="Paper A", year=2024) b = Misc(author=["Smith, B"], title="Paper B", year=2024) path = tmp_path / "refs.bib" write_bibtex([a, b], path) content = path.read_text(encoding="utf-8") assert "Smith.2024.a" in content assert "Smith.2024.b" in content def test_write_bibtex_empty_list(tmp_path): path = tmp_path / "refs.bib" write_bibtex([], path) content = path.read_text(encoding="utf-8") assert content == "" def test_write_bibtex_str_path(tmp_path): a = Misc(author=["Smith, A"], title="Paper", year=2024) path = str(tmp_path / "refs.bib") write_bibtex([a], path) assert Path(path).exists() def test_write_bibtex_missing_parent(): with pytest.raises(FileNotFoundError): write_bibtex( [Misc(author=["Smith, A"], title="Paper", year=2024)], "/nonexistent/dir/refs.bib", ) def test_write_bibtex_utf8(tmp_path): a = Misc( author=["Müller, Hans"], title="Über etwas", year=2020, ) path = tmp_path / "refs.bib" write_bibtex([a], path) content = path.read_text(encoding="utf-8") assert "Müller" in content assert "Über" in content cogent3-citeable-7188740/uv.lock000066400000000000000000002750341515465136300163560ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.11" [[package]] name = "argcomplete" version = "3.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, ] [[package]] name = "attrs" version = "25.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] name = "citeable" version = "2026.3.11b1" source = { editable = "." } [package.dev-dependencies] dev = [ { name = "cogapp" }, { name = "mypy" }, { name = "nox" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, ] [package.metadata] [package.metadata.requires-dev] dev = [ { name = "cogapp" }, { name = "mypy" }, { name = "nox" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, ] [[package]] name = "cogapp" version = "3.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/64/ea/4ffa8095e0b675e9961cbdcad002c09d35d4ab76ff99d61a014e9e6bcd53/cogapp-3.6.0.tar.gz", hash = "sha256:ec2a9170bfa644bf0d91996fdb5576c13d7e5e848bb2378a6f92727b48f92604", size = 59742, upload-time = "2025-09-21T15:54:13.553Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/05/1c/23b5110b7cb305f0fe3a5d6809d6804c1cb57d57b59516dbb5e24aaf95fc/cogapp-3.6.0-py3-none-any.whl", hash = "sha256:6ea11c63221f92978d37d8d76208eadeae2a6e4641d323f165738a5464a19d32", size = 30550, upload-time = "2025-09-21T15:54:12.205Z" }, ] [[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 = "colorlog" version = "6.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, ] [[package]] name = "coverage" version = "7.13.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "dependency-groups" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/55/f054de99871e7beb81935dea8a10b90cd5ce42122b1c3081d5282fdb3621/dependency_groups-1.3.1.tar.gz", hash = "sha256:78078301090517fd938c19f64a53ce98c32834dfe0dee6b88004a569a6adfefd", size = 10093, upload-time = "2025-05-02T00:34:29.452Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/99/c7/d1ec24fb280caa5a79b6b950db565dab30210a66259d17d5bb2b3a9f878d/dependency_groups-1.3.1-py3-none-any.whl", hash = "sha256:51aeaa0dfad72430fcfb7bcdbefbd75f3792e5919563077f30bc0d73f4493030", size = 8664, upload-time = "2025-05-02T00:34:27.085Z" }, ] [[package]] name = "distlib" version = "0.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] name = "filelock" version = "3.24.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, ] [[package]] name = "humanize" version = "4.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "librt" version = "0.8.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd", size = 65315, upload-time = "2026-02-17T16:11:25.152Z" }, { url = "https://files.pythonhosted.org/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965", size = 68021, upload-time = "2026-02-17T16:11:26.129Z" }, { url = "https://files.pythonhosted.org/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da", size = 194500, upload-time = "2026-02-17T16:11:27.177Z" }, { url = "https://files.pythonhosted.org/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0", size = 205622, upload-time = "2026-02-17T16:11:28.242Z" }, { url = "https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e", size = 218304, upload-time = "2026-02-17T16:11:29.344Z" }, { url = "https://files.pythonhosted.org/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3", size = 211493, upload-time = "2026-02-17T16:11:30.445Z" }, { url = "https://files.pythonhosted.org/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac", size = 219129, upload-time = "2026-02-17T16:11:32.021Z" }, { url = "https://files.pythonhosted.org/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596", size = 213113, upload-time = "2026-02-17T16:11:33.192Z" }, { url = "https://files.pythonhosted.org/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99", size = 212269, upload-time = "2026-02-17T16:11:34.373Z" }, { url = "https://files.pythonhosted.org/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe", size = 234673, upload-time = "2026-02-17T16:11:36.063Z" }, { url = "https://files.pythonhosted.org/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb", size = 54597, upload-time = "2026-02-17T16:11:37.544Z" }, { url = "https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b", size = 61733, upload-time = "2026-02-17T16:11:38.691Z" }, { url = "https://files.pythonhosted.org/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9", size = 52273, upload-time = "2026-02-17T16:11:40.308Z" }, { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, ] [[package]] name = "mypy" version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[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 = "nox" version = "2026.2.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, { name = "attrs" }, { name = "colorlog" }, { name = "dependency-groups" }, { name = "humanize" }, { name = "packaging" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/8e/55a9679b31f1efc48facedd2448eb53c7f1e647fb592aa1403c9dd7a4590/nox-2026.2.9.tar.gz", hash = "sha256:1bc8a202ee8cd69be7aaada63b2a7019126899a06fc930a7aee75585bf8ee41b", size = 4031165, upload-time = "2026-02-10T04:38:58.878Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8d/58/0d5e5a044f1868bdc45f38afdc2d90ff9867ce398b4e8fa9e666bfc9bfba/nox-2026.2.9-py3-none-any.whl", hash = "sha256:1b7143bc8ecdf25f2353201326152c5303ae4ae56ca097b1fb6179ad75164c47", size = 74615, upload-time = "2026-02-10T04:38:57.266Z" }, ] [[package]] name = "packaging" version = "26.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pathspec" version = "1.0.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "platformdirs" version = "4.9.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } 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 = "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 = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-cov" version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]] name = "ruff" version = "0.15.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, ] [[package]] name = "tomli" version = "2.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "virtualenv" version = "20.39.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/54/809199edc537dbace273495ac0884d13df26436e910a5ed4d0ec0a69806b/virtualenv-20.39.0.tar.gz", hash = "sha256:a15f0cebd00d50074fd336a169d53422436a12dfe15149efec7072cfe817df8b", size = 5869141, upload-time = "2026-02-23T18:09:13.349Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/b4/8268da45f26f4fe84f6eae80a6ca1485ffb490a926afecff75fc48f61979/virtualenv-20.39.0-py3-none-any.whl", hash = "sha256:44888bba3775990a152ea1f73f8e5f566d49f11bbd1de61d426fd7732770043e", size = 5839121, upload-time = "2026-02-23T18:09:11.173Z" }, ]