pax_global_header 0000666 0000000 0000000 00000000064 15063277303 0014520 g ustar 00root root 0000000 0000000 52 comment=32f23d118cae651a91209fbaa651b0adada60c71
dirty-equals-0.10.0/ 0000775 0000000 0000000 00000000000 15063277303 0014221 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/.codecov.yml 0000664 0000000 0000000 00000000223 15063277303 0016441 0 ustar 00root root 0000000 0000000 coverage:
precision: 2
range: [90, 100]
status:
patch: false
project: false
comment:
layout: 'header, diff, flags, files, footer'
dirty-equals-0.10.0/.github/ 0000775 0000000 0000000 00000000000 15063277303 0015561 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000025 15063277303 0017373 0 ustar 00root root 0000000 0000000 github: samuelcolvin
dirty-equals-0.10.0/.github/workflows/ 0000775 0000000 0000000 00000000000 15063277303 0017616 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000011125 15063277303 0020734 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches:
- main
tags:
- "**"
pull_request: {}
env:
COLUMNS: 150
UV_PYTHON: 3.12
UV_FROZEN: "1"
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- run: uv sync --all-groups
- uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ env.UV_PYTHON }}|${{ hashFiles('.pre-commit-config.yaml') }}
- run: uvx pre-commit run --color=always --all-files --verbose
env:
SKIP: no-commit-to-branch
test:
name: test ${{ matrix.python-version }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu, macos]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
# test pypy on ubuntu only to speed up CI, no reason why macos X pypy should fail separately
include:
- os: "ubuntu"
python-version: "pypy-3.9"
- os: "ubuntu"
python-version: "pypy-3.10"
runs-on: ${{ matrix.os }}-latest
env:
UV_PYTHON: ${{ matrix.python-version }}
OS: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- run: make test
- run: uv run coverage xml
- uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
env_vars: PYTHON,OS
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- run: uv sync --group docs
- name: install mkdocs-material-insiders
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
run: uv pip install https://files.scolvin.com/${MKDOCS_TOKEN}/mkdocs_material-9.4.2+insiders.4.42.0-py3-none-any.whl
env:
MKDOCS_TOKEN: ${{ secrets.mkdocs_token }}
- run: uv run --no-sync mkdocs build --strict
- name: store docs site
uses: actions/upload-artifact@v4
with:
name: docs
path: site
check: # This job does nothing and is only used for the branch protection
if: always()
needs: [lint, test, docs]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
id: all-green
with:
jobs: ${{ toJSON(needs) }}
publish_docs:
needs: [check]
if: "success() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))"
runs-on: ubuntu-latest
steps:
- name: checkout docs-site
uses: actions/checkout@v4
with:
ref: docs-site
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- run: uv sync --group docs
- name: install mkdocs-material-insiders
run: uv pip install https://files.scolvin.com/${MKDOCS_TOKEN}/mkdocs_material-9.4.2+insiders.4.42.0-py3-none-any.whl
env:
MKDOCS_TOKEN: ${{ secrets.mkdocs_token }}
- run: uv run --no-sync mkdocs build --strict
- name: Set git credentials
run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
- run: uv run --no-sync mike deploy -b docs-site dev --push
if: github.ref == 'refs/heads/main'
- name: check version
if: "startsWith(github.ref, 'refs/tags/')"
id: check-version
uses: samuelcolvin/check-python-version@v5
with:
version_file_path: "dirty_equals/version.py"
- run: uv run --no-sync mike deploy -b docs-site ${{ steps.check-version.outputs.VERSION_MAJOR_MINOR }} latest --update-aliases --push
if: "startsWith(github.ref, 'refs/tags/') && !fromJSON(steps.check-version.outputs.IS_PRERELEASE)"
deploy:
needs: [check]
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: install
run: pip install -U build
- name: check version
id: check-version
uses: samuelcolvin/check-python-version@v5
with:
version_file_path: "dirty_equals/version.py"
- run: uv build
- run: uv publish --trusted-publishing always
dirty-equals-0.10.0/.github/workflows/upload-previews.yml 0000664 0000000 0000000 00000001626 15063277303 0023474 0 ustar 00root root 0000000 0000000 name: Upload Previews
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
statuses: write
jobs:
upload-previews:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v1
with:
python-version: '3.10'
- run: pip install click==8.0.4
- run: pip install smokeshow
- uses: dawidd6/action-download-artifact@v11
with:
workflow: ci.yml
commit: ${{ github.event.workflow_run.head_sha }}
- run: smokeshow upload docs
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Docs Preview
SMOKESHOW_GITHUB_CONTEXT: docs
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.github_token }}
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
SMOKESHOW_AUTH_KEY: ${{ secrets.smokeshow_auth_key }}
dirty-equals-0.10.0/.gitignore 0000664 0000000 0000000 00000000326 15063277303 0016212 0 ustar 00root root 0000000 0000000 *.py[cod]
.idea/
env/
env*/
.coverage
.cache/
htmlcov/
media/
sandbox/
.pytest_cache/
*.egg-info/
/build/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/TODO.md
/.mypy_cache/
/.ruff_cache/
/scratch/
/site/
dirty-equals-0.10.0/.pre-commit-config.yaml 0000664 0000000 0000000 00000001176 15063277303 0020507 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: no-commit-to-branch
- id: check-yaml
args: ['--unsafe']
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
- repo: local
hooks:
- id: format
name: Format
entry: make format
types: [python]
language: system
pass_filenames: false
- id: lint
name: Lint
entry: make lint
types: [python]
language: system
pass_filenames: false
- id: mypy
name: Mypy
entry: make mypy
types: [python]
language: system
pass_filenames: false
dirty-equals-0.10.0/LICENSE 0000664 0000000 0000000 00000002103 15063277303 0015222 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2022 to present Samuel Colvin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
dirty-equals-0.10.0/Makefile 0000664 0000000 0000000 00000003314 15063277303 0015662 0 ustar 00root root 0000000 0000000 .DEFAULT_GOAL := all
.PHONY: .uv
.uv: ## Check that uv is installed
@uv --version || echo 'Please install uv: https://docs.astral.sh/uv/getting-started/installation/'
.PHONY: .pre-commit
.pre-commit: ## Check that pre-commit is installed
@pre-commit -V || echo 'Please install pre-commit: https://pre-commit.com/'
.PHONY: install
install: .uv .pre-commit ## Install the package, dependencies, and pre-commit for local development
uv sync --frozen --all-groups
pre-commit install --install-hooks
.PHONY: format
format: ## Format the code
uv run ruff format
uv run ruff check --fix --fix-only
.PHONY: lint
lint: ## Lint the code
uv run ruff format --check
uv run ruff check
.PHONY: test
test:
TZ=utc uv run coverage run -m pytest
uv run tests/mypy_checks.py
.PHONY: testcov
testcov: test
@uv run coverage report --show-missing
@uv run coverage html
.PHONY: mypy
mypy:
uv run mypy dirty_equals tests/mypy_checks.py
.PHONY: docs
docs:
uv run --group docs mkdocs build --strict
.PHONY: all
all: lint mypy testcov docs
.PHONY: clean
clean:
rm -rf `find . -name __pycache__`
rm -f `find . -type f -name '*.py[co]' `
rm -f `find . -type f -name '*~' `
rm -f `find . -type f -name '.*~' `
rm -rf .cache
rm -rf .pytest_cache
rm -rf .mypy_cache
rm -rf htmlcov
rm -rf *.egg-info
rm -f .coverage
rm -f .coverage.*
rm -rf build
.PHONY: help
help: ## Show this help (usage: make help)
@echo "Usage: make [recipe]"
@echo "Recipes:"
@awk '/^[a-zA-Z0-9_-]+:.*?##/ { \
helpMessage = match($$0, /## (.*)/); \
if (helpMessage) { \
recipe = $$1; \
sub(/:/, "", recipe); \
printf " \033[36m%-20s\033[0m %s\n", recipe, substr($$0, RSTART + 3, RLENGTH); \
} \
}' $(MAKEFILE_LIST)
dirty-equals-0.10.0/README.md 0000664 0000000 0000000 00000007571 15063277303 0015512 0 ustar 00root root 0000000 0000000
Doing dirty (but extremely useful) things with equals.
---
**Documentation**: [dirty-equals.helpmanual.io](https://dirty-equals.helpmanual.io)
**Source Code**: [github.com/samuelcolvin/dirty-equals](https://github.com/samuelcolvin/dirty-equals)
---
**dirty-equals** is a python library that (mis)uses the `__eq__` method to make python code (generally unit tests)
more declarative and therefore easier to read and write.
*dirty-equals* can be used in whatever context you like, but it comes into its own when writing unit tests for
applications where you're commonly checking the response to API calls and the contents of a database.
## Usage
Here's a trivial example of what *dirty-equals* can do:
```py
from dirty_equals import IsPositive
assert 1 == IsPositive
assert -2 == IsPositive # this will fail!
```
**That doesn't look very useful yet!**, but consider the following unit test code using *dirty-equals*:
```py title="More Powerful Usage"
from dirty_equals import IsJson, IsNow, IsPositiveInt, IsStr
...
# user_data is a dict returned from a database or API which we want to test
assert user_data == {
# we want to check that id is a positive int
'id': IsPositiveInt,
# we know avatar_file should be a string, but we need a regex as we don't know whole value
'avatar_file': IsStr(regex=r'/[a-z0-9\-]{10}/example\.png'),
# settings_json is JSON, but it's more robust to compare the value it encodes, not strings
'settings_json': IsJson({'theme': 'dark', 'language': 'en'}),
# created_ts is datetime, we don't know the exact value, but we know it should be close to now
'created_ts': IsNow(delta=3),
}
```
Without *dirty-equals*, you'd have to compare individual fields and/or modify some fields before comparison -
the test would not be declarative or as clear.
*dirty-equals* can do so much more than that, for example:
* [`IsPartialDict`](https://dirty-equals.helpmanual.io/types/dict/#dirty_equals.IsPartialDict)
lets you compare a subset of a dictionary
* [`IsStrictDict`](https://dirty-equals.helpmanual.io/types/dict/#dirty_equals.IsStrictDict)
lets you confirm order in a dictionary
* [`IsList`](https://dirty-equals.helpmanual.io/types/sequence/#dirty_equals.IsList) and
[`IsTuple`](https://dirty-equals.helpmanual.io/types/sequence/#dirty_equals.IsTuple)
lets you compare partial lists and tuples, with or without order constraints
* nesting any of these types inside any others
* [`IsInstance`](https://dirty-equals.helpmanual.io/types/other/#dirty_equals.IsInstance)
lets you simply confirm the type of an object
* You can even use [boolean operators](https://dirty-equals.helpmanual.io/usage/#boolean-logic)
`|` and `&` to combine multiple conditions
* and much more...
## Installation
Simply:
```bash
pip install dirty-equals
```
**dirty-equals** requires **Python 3.9+**.
dirty-equals-0.10.0/dirty_equals/ 0000775 0000000 0000000 00000000000 15063277303 0016726 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/dirty_equals/__init__.py 0000664 0000000 0000000 00000004066 15063277303 0021045 0 ustar 00root root 0000000 0000000 from ._base import AnyThing, DirtyEquals, IsOneOf
from ._boolean import IsFalseLike, IsTrueLike
from ._datetime import IsDate, IsDatetime, IsNow, IsToday
from ._dict import IsDict, IsIgnoreDict, IsPartialDict, IsStrictDict
from ._inspection import HasAttributes, HasName, HasRepr, IsInstance
from ._numeric import (
IsApprox,
IsFloat,
IsFloatInf,
IsFloatInfNeg,
IsFloatInfPos,
IsFloatNan,
IsInt,
IsNegative,
IsNegativeFloat,
IsNegativeInt,
IsNonNegative,
IsNonPositive,
IsNumber,
IsNumeric,
IsPositive,
IsPositiveFloat,
IsPositiveInt,
)
from ._other import (
FunctionCheck,
IsDataclass,
IsDataclassType,
IsEnum,
IsHash,
IsIP,
IsJson,
IsPartialDataclass,
IsStrictDataclass,
IsUrl,
IsUUID,
)
from ._sequence import Contains, HasLen, IsList, IsListOrTuple, IsTuple
from ._strings import IsAnyStr, IsBytes, IsStr
from .version import VERSION
__all__ = (
# base
'DirtyEquals',
'AnyThing',
'IsOneOf',
# boolean
'IsTrueLike',
'IsFalseLike',
# dataclass
'IsDataclass',
'IsDataclassType',
'IsPartialDataclass',
'IsStrictDataclass',
# datetime
'IsDatetime',
'IsNow',
'IsDate',
'IsToday',
# dict
'IsDict',
'IsPartialDict',
'IsIgnoreDict',
'IsStrictDict',
# enum
'IsEnum',
# sequence
'Contains',
'HasLen',
'IsList',
'IsTuple',
'IsListOrTuple',
# numeric
'IsNumeric',
'IsApprox',
'IsNumber',
'IsPositive',
'IsNegative',
'IsNonPositive',
'IsNonNegative',
'IsInt',
'IsPositiveInt',
'IsNegativeInt',
'IsFloat',
'IsPositiveFloat',
'IsNegativeFloat',
'IsFloatInf',
'IsFloatInfNeg',
'IsFloatInfPos',
'IsFloatNan',
# inspection
'HasAttributes',
'HasName',
'HasRepr',
'IsInstance',
# other
'FunctionCheck',
'IsJson',
'IsUUID',
'IsUrl',
'IsHash',
'IsIP',
# strings
'IsStr',
'IsBytes',
'IsAnyStr',
# version
'__version__',
)
__version__ = VERSION
dirty-equals-0.10.0/dirty_equals/_base.py 0000664 0000000 0000000 00000020043 15063277303 0020350 0 ustar 00root root 0000000 0000000 import io
from abc import ABCMeta
from collections.abc import Iterable
from pprint import PrettyPrinter
from typing import TYPE_CHECKING, Any, Generic, Optional, Protocol, TypeVar
from ._utils import Omit
if TYPE_CHECKING:
from typing import TypeAlias, Union # noqa: F401
__all__ = 'DirtyEqualsMeta', 'DirtyEquals', 'AnyThing', 'IsOneOf'
class DirtyEqualsMeta(ABCMeta):
def __eq__(self, other: Any) -> bool:
if self is other:
return True
# this is required as fancy things happen when creating generics which include equals checks, without it,
# we get some recursive errors
if self is DirtyEquals or other is Generic or other is Protocol:
return False
else:
try:
return self() == other
except TypeError:
# we don't want to raise a type error here since somewhere deep in pytest it does something like
# type(a) == type(b), if we raised TypeError we would upset the pytest error message
return False
def __or__(self, other: Any) -> 'DirtyOr': # type: ignore[override]
return DirtyOr(self, other)
def __and__(self, other: Any) -> 'DirtyAnd':
return DirtyAnd(self, other)
def __invert__(self) -> 'DirtyNot':
return DirtyNot(self)
def __hash__(self) -> int:
return hash(self.__name__)
def __repr__(self) -> str:
return self.__name__
T = TypeVar('T')
class DirtyEquals(Generic[T], metaclass=DirtyEqualsMeta):
"""
Base type for all *dirty-equals* types.
"""
__slots__ = '_other', '_was_equal', '_repr_args', '_repr_kwargs'
def __init__(self, *repr_args: Any, **repr_kwargs: Any):
"""
Args:
*repr_args: unnamed args to be used in `__repr__`
**repr_kwargs: named args to be used in `__repr__`
"""
self._other: Any = None
self._was_equal: Optional[bool] = None
self._repr_args: Iterable[Any] = repr_args
self._repr_kwargs: dict[str, Any] = repr_kwargs
def equals(self, other: Any) -> bool:
"""
Abstract method, must be implemented by subclasses.
`TypeError` and `ValueError` are caught in `__eq__` and indicate `other` is not equals to this type.
"""
raise NotImplementedError()
@property
def value(self) -> T:
"""
Property to get the value last successfully compared to this object.
This is seldom very useful, put it's provided for completeness.
Example of usage:
```py title=".values"
from dirty_equals import IsStr
token_is_str = IsStr(regex=r't-.+')
assert 't-123' == token_is_str
print(token_is_str.value)
#> t-123
```
"""
if self._was_equal:
return self._other
else:
raise AttributeError('value is not available until __eq__ has been called')
def __eq__(self, other: Any) -> bool:
self._other = other
try:
self._was_equal = self.equals(other)
except (TypeError, ValueError):
self._was_equal = False
return self._was_equal
def __ne__(self, other: Any) -> bool:
# We don't set _was_equal to avoid strange errors in pytest
self._other = other
try:
return not self.equals(other)
except (TypeError, ValueError):
return True
def __or__(self, other: Any) -> 'DirtyOr':
return DirtyOr(self, other)
def __and__(self, other: Any) -> 'DirtyAnd':
return DirtyAnd(self, other)
def __invert__(self) -> 'DirtyNot':
return DirtyNot(self)
def _repr_ne(self) -> str:
args = [repr(arg) for arg in self._repr_args if arg is not Omit]
args += [f'{k}={v!r}' for k, v in self._repr_kwargs.items() if v is not Omit]
return f'{self.__class__.__name__}({", ".join(args)})'
def __repr__(self) -> str:
if self._was_equal:
# if we've got the correct value return it to aid in diffs
return repr(self._other)
else:
# else return something which explains what's going on.
return self._repr_ne()
def _pprint_format(self, pprinter: PrettyPrinter, stream: io.StringIO, *args: Any, **kwargs: Any) -> None:
# pytest diffs use pprint to format objects, so we patch pprint to call this method
# for DirtyEquals objects. So this method needs to follow the same pattern as __repr__.
# We check that the protected _format method actually exists
# to be safe and to make linters happy.
if self._was_equal and hasattr(pprinter, '_format'):
pprinter._format(self._other, stream, *args, **kwargs)
else:
stream.write(repr(self)) # i.e. self._repr_ne() (for now)
# Patch pprint to call _pprint_format for DirtyEquals objects
# Check that the protected attribute _dispatch exists to be safe and to make linters happy.
# The reason we modify _dispatch rather than _format
# is that pytest sometimes uses a subclass of PrettyPrinter which overrides _format.
if hasattr(PrettyPrinter, '_dispatch'): # pragma: no branch
PrettyPrinter._dispatch[DirtyEquals.__repr__] = lambda pprinter, obj, *args, **kwargs: obj._pprint_format(
pprinter, *args, **kwargs
)
InstanceOrType: 'TypeAlias' = 'Union[DirtyEquals[Any], DirtyEqualsMeta]'
class DirtyOr(DirtyEquals[Any]):
def __init__(self, a: 'InstanceOrType', b: 'InstanceOrType', *extra: 'InstanceOrType'):
self.dirties = (a, b) + extra
super().__init__()
def equals(self, other: Any) -> bool:
return any(d == other for d in self.dirties)
def _repr_ne(self) -> str:
return ' | '.join(_repr_ne(d) for d in self.dirties)
class DirtyAnd(DirtyEquals[Any]):
def __init__(self, a: InstanceOrType, b: InstanceOrType, *extra: InstanceOrType):
self.dirties = (a, b) + extra
super().__init__()
def equals(self, other: Any) -> bool:
return all(d == other for d in self.dirties)
def _repr_ne(self) -> str:
return ' & '.join(_repr_ne(d) for d in self.dirties)
class DirtyNot(DirtyEquals[Any]):
def __init__(self, subject: InstanceOrType):
self.subject = subject
super().__init__()
def equals(self, other: Any) -> bool:
return self.subject != other
def _repr_ne(self) -> str:
return f'~{_repr_ne(self.subject)}'
def _repr_ne(v: InstanceOrType) -> str:
if isinstance(v, DirtyEqualsMeta):
return repr(v)
else:
return v._repr_ne()
class AnyThing(DirtyEquals[Any]):
"""
A type which matches any value. `AnyThing` isn't generally very useful on its own, but can be used within
other comparisons.
```py title="AnyThing"
from dirty_equals import AnyThing, IsList, IsStrictDict
assert 1 == AnyThing
assert 'foobar' == AnyThing
assert [1, 2, 3] == AnyThing
assert [1, 2, 3] == IsList(AnyThing, 2, 3)
assert {'a': 1, 'b': 2, 'c': 3} == IsStrictDict(a=1, b=AnyThing, c=3)
```
"""
def equals(self, other: Any) -> bool:
return True
class IsOneOf(DirtyEquals[Any]):
"""
A type which checks that the value is equal to one of the given values.
Can be useful with boolean operators.
"""
def __init__(self, expected_value: Any, *more_expected_values: Any):
"""
Args:
expected_value: Expected value for equals to return true.
*more_expected_values: More expected values for equals to return true.
```py title="IsOneOf"
from dirty_equals import Contains, IsOneOf
assert 1 == IsOneOf(1, 2, 3)
assert 4 != IsOneOf(1, 2, 3)
# check that a list either contain 1 or is empty
assert [1, 2, 3] == Contains(1) | IsOneOf([])
assert [] == Contains(1) | IsOneOf([])
```
"""
self.expected_values: tuple[Any, ...] = (expected_value,) + more_expected_values
super().__init__(*self.expected_values)
def equals(self, other: Any) -> bool:
return any(other == e for e in self.expected_values)
dirty-equals-0.10.0/dirty_equals/_boolean.py 0000664 0000000 0000000 00000004560 15063277303 0021063 0 ustar 00root root 0000000 0000000 from typing import Any
from ._base import DirtyEquals
from ._utils import Omit
class IsTrueLike(DirtyEquals[bool]):
"""
Check if the value is True like. `IsTrueLike` allows comparison to anything and effectively uses just
`return bool(other)`.
Example of basic usage:
```py title="IsTrueLike"
from dirty_equals import IsTrueLike
assert True == IsTrueLike
assert 1 == IsTrueLike
assert 'true' == IsTrueLike
assert 'foobar' == IsTrueLike # any non-empty string is "True"
assert '' != IsTrueLike
assert [1] == IsTrueLike
assert {} != IsTrueLike
assert None != IsTrueLike
```
"""
def equals(self, other: Any) -> bool:
return bool(other)
class IsFalseLike(DirtyEquals[bool]):
"""
Check if the value is False like. `IsFalseLike` allows comparison to anything and effectively uses
`return not bool(other)` (with string checks if `allow_strings=True` is set).
"""
def __init__(self, *, allow_strings: bool = False):
"""
Args:
allow_strings: if `True`, allow comparisons to `False` like strings, case-insensitive, allows
`''`, `'false'` and any string where `float(other) == 0` (e.g. `'0'`).
Example of basic usage:
```py title="IsFalseLike"
from dirty_equals import IsFalseLike
assert False == IsFalseLike
assert 0 == IsFalseLike
assert 'false' == IsFalseLike(allow_strings=True)
assert '0' == IsFalseLike(allow_strings=True)
assert 'foobar' != IsFalseLike(allow_strings=True)
assert 'false' != IsFalseLike
assert 'True' != IsFalseLike(allow_strings=True)
assert [1] != IsFalseLike
assert {} == IsFalseLike
assert None == IsFalseLike
assert '' == IsFalseLike(allow_strings=True)
assert '' == IsFalseLike
```
"""
self.allow_strings = allow_strings
super().__init__(allow_strings=allow_strings or Omit)
def equals(self, other: Any) -> bool:
if isinstance(other, str) and self.allow_strings:
return self.make_string_check(other)
return not bool(other)
@staticmethod
def make_string_check(other: str) -> bool:
if other.lower() in {'false', ''}:
return True
try:
return float(other) == 0
except ValueError:
return False
dirty-equals-0.10.0/dirty_equals/_datetime.py 0000664 0000000 0000000 00000025710 15063277303 0021240 0 ustar 00root root 0000000 0000000 from __future__ import annotations as _annotations
from datetime import date, datetime, timedelta, timezone, tzinfo
from typing import Any
from zoneinfo import ZoneInfo
from ._numeric import IsNumeric
from ._utils import Omit
class IsDatetime(IsNumeric[datetime]):
"""
Check if the value is a datetime, and matches the given conditions.
"""
allowed_types = datetime
def __init__(
self,
*,
approx: datetime | None = None,
delta: timedelta | int | float | None = None,
gt: datetime | None = None,
lt: datetime | None = None,
ge: datetime | None = None,
le: datetime | None = None,
unix_number: bool = False,
iso_string: bool = False,
format_string: str | None = None,
enforce_tz: bool = True,
):
"""
Args:
approx: A value to approximately compare to.
delta: The allowable different when comparing to the value to `approx`, if omitted 2 seconds is used,
ints and floats are assumed to represent seconds and converted to `timedelta`s.
gt: Value which the compared value should be greater than (after).
lt: Value which the compared value should be less than (before).
ge: Value which the compared value should be greater than (after) or equal to.
le: Value which the compared value should be less than (before) or equal to.
unix_number: whether to allow unix timestamp numbers in comparison
iso_string: whether to allow iso formatted strings in comparison
format_string: if provided, `format_string` is used with `datetime.strptime` to parse strings
enforce_tz: whether timezone should be enforced in comparison, see below for more details
Examples of basic usage:
```py title="IsDatetime"
from datetime import datetime
from dirty_equals import IsDatetime
y2k = datetime(2000, 1, 1)
assert datetime(2000, 1, 1) == IsDatetime(approx=y2k)
# Note: this requires the system timezone to be UTC
assert 946684800.123 == IsDatetime(approx=y2k, unix_number=True)
assert datetime(2000, 1, 1, 0, 0, 9) == IsDatetime(approx=y2k, delta=10)
assert '2000-01-01T00:00' == IsDatetime(approx=y2k, iso_string=True)
assert datetime(2000, 1, 2) == IsDatetime(gt=y2k)
assert datetime(1999, 1, 2) != IsDatetime(gt=y2k)
```
"""
if isinstance(delta, (int, float)):
delta = timedelta(seconds=delta)
super().__init__(
approx=approx,
delta=delta, # type: ignore[arg-type]
gt=gt,
lt=lt,
ge=ge,
le=le,
)
self.unix_number = unix_number
self.iso_string = iso_string
self.format_string = format_string
self.enforce_tz = enforce_tz
self._repr_kwargs.update(
unix_number=Omit if unix_number is False else unix_number,
iso_string=Omit if iso_string is False else iso_string,
format_string=Omit if format_string is None else format_string,
enforce_tz=Omit if enforce_tz is True else format_string,
)
def prepare(self, other: Any) -> datetime:
if isinstance(other, datetime):
dt = other
elif isinstance(other, (float, int)):
if self.unix_number:
dt = datetime.fromtimestamp(other)
else:
raise TypeError('numbers not allowed')
elif isinstance(other, str):
if self.iso_string:
dt = datetime.fromisoformat(other)
elif self.format_string:
dt = datetime.strptime(other, self.format_string)
else:
raise ValueError('not a valid datetime string')
else:
raise ValueError(f'{type(other)} not valid as datetime')
if self.approx is not None and not self.enforce_tz and self.approx.tzinfo is None and dt.tzinfo is not None:
dt = dt.replace(tzinfo=None)
return dt
def approx_equals(self, other: datetime, delta: timedelta) -> bool:
if not super().approx_equals(other, delta):
return False
if self.enforce_tz:
if self.approx.tzinfo is None: # type: ignore[union-attr]
return other.tzinfo is None
else:
approx_offset = self.approx.tzinfo.utcoffset(self.approx) # type: ignore[union-attr]
other_offset = other.tzinfo.utcoffset(other) # type: ignore[union-attr]
return approx_offset == other_offset
else:
return True
class IsNow(IsDatetime):
"""
Check if a datetime is close to now, this is similar to `IsDatetime(approx=datetime.now())`,
but slightly more powerful.
"""
def __init__(
self,
*,
delta: timedelta | int | float = 2,
unix_number: bool = False,
iso_string: bool = False,
format_string: str | None = None,
enforce_tz: bool = True,
tz: str | tzinfo | None = None,
):
"""
Args:
delta: The allowable different when comparing to the value to now, if omitted 2 seconds is used,
ints and floats are assumed to represent seconds and converted to `timedelta`s.
unix_number: whether to allow unix timestamp numbers in comparison
iso_string: whether to allow iso formatted strings in comparison
format_string: if provided, `format_string` is used with `datetime.strptime` to parse strings
enforce_tz: whether timezone should be enforced in comparison, see below for more details
tz: either a `ZoneInfo`, a `datetime.timezone` or a string which will be passed to `ZoneInfo`,
to get a timezone, if provided now will be converted to this timezone.
```py title="IsNow"
from datetime import datetime, timezone
from dirty_equals import IsNow
now = datetime.now()
assert now == IsNow
assert now.timestamp() == IsNow(unix_number=True)
assert now.timestamp() != IsNow
assert now.isoformat() == IsNow(iso_string=True)
assert now.isoformat() != IsNow
utc_now = datetime.utcnow().replace(tzinfo=timezone.utc)
assert utc_now == IsNow(tz=timezone.utc)
```
"""
if isinstance(tz, str):
tz = ZoneInfo(tz)
self.tz = tz
approx = self._get_now()
super().__init__(
approx=approx,
delta=delta,
unix_number=unix_number,
iso_string=iso_string,
format_string=format_string,
enforce_tz=enforce_tz,
)
if tz is not None:
self._repr_kwargs['tz'] = tz
def _get_now(self) -> datetime:
if self.tz is None:
return datetime.now()
else:
utc_now = datetime.now(tz=timezone.utc).replace(tzinfo=timezone.utc)
return utc_now.astimezone(self.tz)
def prepare(self, other: Any) -> datetime:
# update approx for every comparing, to check if other value is dirty equal
# to current moment of time
self.approx = self._get_now()
return super().prepare(other)
class IsDate(IsNumeric[date]):
"""
Check if the value is a date, and matches the given conditions.
"""
allowed_types = date
def __init__(
self,
*,
approx: date | None = None,
delta: timedelta | int | float | None = None,
gt: date | None = None,
lt: date | None = None,
ge: date | None = None,
le: date | None = None,
iso_string: bool = False,
format_string: str | None = None,
):
"""
Args:
approx: A value to approximately compare to.
delta: The allowable different when comparing to the value to now, if omitted 2 seconds is used,
ints and floats are assumed to represent seconds and converted to `timedelta`s.
gt: Value which the compared value should be greater than (after).
lt: Value which the compared value should be less than (before).
ge: Value which the compared value should be greater than (after) or equal to.
le: Value which the compared value should be less than (before) or equal to.
iso_string: whether to allow iso formatted strings in comparison
format_string: if provided, `format_string` is used with `datetime.strptime` to parse strings
Examples of basic usage:
```py title="IsDate"
from datetime import date
from dirty_equals import IsDate
y2k = date(2000, 1, 1)
assert date(2000, 1, 1) == IsDate(approx=y2k)
assert '2000-01-01' == IsDate(approx=y2k, iso_string=True)
assert date(2000, 1, 2) == IsDate(gt=y2k)
assert date(1999, 1, 2) != IsDate(gt=y2k)
```
"""
if delta is None:
delta = timedelta()
elif isinstance(delta, (int, float)):
delta = timedelta(seconds=delta)
super().__init__(approx=approx, gt=gt, lt=lt, ge=ge, le=le, delta=delta) # type: ignore[arg-type]
self.iso_string = iso_string
self.format_string = format_string
self._repr_kwargs.update(
iso_string=Omit if iso_string is False else iso_string,
format_string=Omit if format_string is None else format_string,
)
def prepare(self, other: Any) -> date:
if type(other) is date:
dt = other
elif isinstance(other, str):
if self.iso_string:
dt = date.fromisoformat(other)
elif self.format_string:
dt = datetime.strptime(other, self.format_string).date()
else:
raise ValueError('not a valid date string')
else:
raise ValueError(f'{type(other)} not valid as date')
return dt
class IsToday(IsDate):
"""
Check if a date is today, this is similar to `IsDate(approx=date.today())`, but slightly more powerful.
"""
def __init__(
self,
*,
iso_string: bool = False,
format_string: str | None = None,
):
"""
Args:
iso_string: whether to allow iso formatted strings in comparison
format_string: if provided, `format_string` is used with `datetime.strptime` to parse strings
```py title="IsToday"
from datetime import date, timedelta
from dirty_equals import IsToday
today = date.today()
assert today == IsToday
assert today.isoformat() == IsToday(iso_string=True)
assert today.isoformat() != IsToday
assert today + timedelta(days=1) != IsToday
assert today.strftime('%Y/%m/%d') == IsToday(format_string='%Y/%m/%d')
assert today.strftime('%Y/%m/%d') != IsToday()
```
"""
super().__init__(approx=date.today(), iso_string=iso_string, format_string=format_string)
dirty-equals-0.10.0/dirty_equals/_dict.py 0000664 0000000 0000000 00000020135 15063277303 0020363 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from collections.abc import Container
from typing import Any, Callable, overload
from ._base import DirtyEquals, DirtyEqualsMeta
from ._utils import get_dict_arg
NotGiven = object()
class IsDict(DirtyEquals[dict[Any, Any]]):
"""
Base class for comparing dictionaries. By default, `IsDict` isn't particularly useful on its own
(it behaves pretty much like a normal `dict`), but it can be subclassed
(see [`IsPartialDict`][dirty_equals.IsPartialDict] and [`IsStrictDict`][dirty_equals.IsStrictDict]) or modified
with `.settings(...)` to powerful things.
"""
@overload
def __init__(self, expected: dict[Any, Any]): ...
@overload
def __init__(self, **expected: Any): ...
def __init__(self, *expected_args: dict[Any, Any], **expected_kwargs: Any):
"""
Can be created from either keyword arguments or an existing dictionary (same as `dict()`).
`IsDict` is not particularly useful on its own, but it can be subclassed or modified with
[`.settings(...)`][dirty_equals.IsDict.settings] to facilitate powerful comparison of dictionaries.
```py title="IsDict"
from dirty_equals import IsDict
assert {'a': 1, 'b': 2} == IsDict(a=1, b=2)
assert {1: 2, 3: 4} == IsDict({1: 2, 3: 4})
```
"""
self.expected_values = get_dict_arg('IsDict', expected_args, expected_kwargs)
self.strict = False
self.partial = False
self.ignore: None | Container[Any] | Callable[[Any], bool] = None
self._post_init()
super().__init__()
def _post_init(self) -> None:
pass
def settings(
self,
*,
strict: bool | None = None,
partial: bool | None = None,
ignore: None | Container[Any] | Callable[[Any], bool] = NotGiven, # type: ignore[assignment]
) -> IsDict:
"""
Allows you to customise the behaviour of `IsDict`, technically a new `IsDict` is required to allow chaining.
Args:
strict (bool): If `True`, the order of key/value pairs must match.
partial (bool): If `True`, only keys include in the wrapped dict are checked.
ignore (Union[None, Container[Any], Callable[[Any], bool]]): Values to omit from comparison.
Can be either a `Container` (e.g. `set` or `list`) of values to ignore, or a function that takes a
value and should return `True` if the value should be ignored.
```py title="IsDict.settings(...)"
from dirty_equals import IsDict
assert {'a': 1, 'b': 2, 'c': None} != IsDict(a=1, b=2)
assert {'a': 1, 'b': 2, 'c': None} == IsDict(a=1, b=2).settings(partial=True) # (1)!
assert {'b': 2, 'a': 1} == IsDict(a=1, b=2)
assert {'b': 2, 'a': 1} != IsDict(a=1, b=2).settings(strict=True) # (2)!
# combining partial and strict
assert {'a': 1, 'b': None, 'c': 3} == IsDict(a=1, c=3).settings(
strict=True, partial=True
)
assert {'b': None, 'c': 3, 'a': 1} != IsDict(a=1, c=3).settings(
strict=True, partial=True
)
```
1. This is the same as [`IsPartialDict(a=1, b=2)`][dirty_equals.IsPartialDict]
2. This is the same as [`IsStrictDict(a=1, b=2)`][dirty_equals.IsStrictDict]
"""
new_cls = self.__class__(self.expected_values)
new_cls.__dict__ = self.__dict__.copy()
if strict is not None:
new_cls.strict = strict
if partial is not None:
new_cls.partial = partial
if ignore is not NotGiven:
new_cls.ignore = ignore
if new_cls.partial and new_cls.ignore:
raise TypeError('partial and ignore cannot be used together')
return new_cls
def equals(self, other: dict[Any, Any]) -> bool:
if not isinstance(other, dict):
return False
expected = self.expected_values
if self.partial:
other = {k: v for k, v in other.items() if k in expected}
if self.ignore:
expected = self._filter_dict(self.expected_values)
other = self._filter_dict(other)
if other != expected:
return False
if self.strict and list(other.keys()) != list(expected.keys()):
return False
return True
def _filter_dict(self, d: dict[Any, Any]) -> dict[Any, Any]:
return {k: v for k, v in d.items() if not self._ignore_value(v)}
def _ignore_value(self, v: Any) -> bool:
# `isinstance(v, (DirtyEquals, DirtyEqualsMeta))` seems to always return `True` on pypy, no idea why
if type(v) in (DirtyEquals, DirtyEqualsMeta):
return False
elif callable(self.ignore):
return self.ignore(v)
else:
try:
return v in self.ignore # type: ignore[operator]
except TypeError:
# happens for unhashable types
return False
def _repr_ne(self) -> str:
name = self.__class__.__name__
modifiers = []
if self.partial != (name == 'IsPartialDict'):
modifiers += [f'partial={self.partial}']
if (self.ignore == {None}) != (name == 'IsIgnoreDict') or self.ignore not in (None, {None}):
r = self.ignore.__name__ if callable(self.ignore) else repr(self.ignore)
modifiers += [f'ignore={r}']
if self.strict != (name == 'IsStrictDict'):
modifiers += [f'strict={self.strict}']
if modifiers:
mod = f'[{", ".join(modifiers)}]'
else:
mod = ''
args = [f'{k}={v!r}' for k, v in self.expected_values.items()]
return f'{name}{mod}({", ".join(args)})'
class IsPartialDict(IsDict):
"""
Partial dictionary comparison, this is the same as
[`IsDict(...).settings(partial=True)`][dirty_equals.IsDict.settings].
```py title="IsPartialDict"
from dirty_equals import IsPartialDict
assert {'a': 1, 'b': 2, 'c': 3} == IsPartialDict(a=1, b=2)
assert {'a': 1, 'b': 2, 'c': 3} != IsPartialDict(a=1, b=3)
assert {'a': 1, 'b': 2, 'd': 3} != IsPartialDict(a=1, b=2, c=3)
# combining partial and strict
assert {'a': 1, 'b': None, 'c': 3} == IsPartialDict(a=1, c=3).settings(strict=True)
assert {'b': None, 'c': 3, 'a': 1} != IsPartialDict(a=1, c=3).settings(strict=True)
```
"""
def _post_init(self) -> None:
self.partial = True
class IsIgnoreDict(IsDict):
"""
Dictionary comparison with `None` values ignored, this is the same as
[`IsDict(...).settings(ignore={None})`][dirty_equals.IsDict.settings].
`.settings(...)` can be used to customise the behaviour of `IsIgnoreDict`, in particular changing which
values are ignored.
```py title="IsIgnoreDict"
from dirty_equals import IsIgnoreDict
assert {'a': 1, 'b': 2, 'c': None} == IsIgnoreDict(a=1, b=2)
assert {'a': 1, 'b': 2, 'c': 'ignore'} == (
IsIgnoreDict(a=1, b=2).settings(ignore={None, 'ignore'})
)
def is_even(v: int) -> bool:
return v % 2 == 0
assert {'a': 1, 'b': 2, 'c': 3, 'd': 4} == (
IsIgnoreDict(a=1, c=3).settings(ignore=is_even)
)
# combining partial and strict
assert {'a': 1, 'b': None, 'c': 3} == IsIgnoreDict(a=1, c=3).settings(strict=True)
assert {'b': None, 'c': 3, 'a': 1} != IsIgnoreDict(a=1, c=3).settings(strict=True)
```
"""
def _post_init(self) -> None:
self.ignore = {None}
class IsStrictDict(IsDict):
"""
Dictionary comparison with order enforced, this is the same as
[`IsDict(...).settings(strict=True)`][dirty_equals.IsDict.settings].
```py title="IsDict.settings(...)"
from dirty_equals import IsStrictDict
assert {'a': 1, 'b': 2} == IsStrictDict(a=1, b=2)
assert {'a': 1, 'b': 2, 'c': 3} != IsStrictDict(a=1, b=2)
assert {'b': 2, 'a': 1} != IsStrictDict(a=1, b=2)
# combining partial and strict
assert {'a': 1, 'b': None, 'c': 3} == IsStrictDict(a=1, c=3).settings(partial=True)
assert {'b': None, 'c': 3, 'a': 1} != IsStrictDict(a=1, c=3).settings(partial=True)
```
"""
def _post_init(self) -> None:
self.strict = True
dirty-equals-0.10.0/dirty_equals/_inspection.py 0000664 0000000 0000000 00000014562 15063277303 0021622 0 ustar 00root root 0000000 0000000 from typing import Any, TypeVar, Union, overload
from ._base import DirtyEquals
from ._strings import IsStr
from ._utils import get_dict_arg
ExpectedType = TypeVar('ExpectedType', bound=Union[type, tuple[Union[type, tuple[Any, ...]], ...]])
class IsInstance(DirtyEquals[ExpectedType]):
"""
A type which checks that the value is an instance of the expected type.
"""
def __init__(self, expected_type: ExpectedType, *, only_direct_instance: bool = False):
"""
Args:
expected_type: The type to check against.
only_direct_instance: whether instances of subclasses of `expected_type` should be considered equal.
!!! note
`IsInstance` can be parameterized or initialised with a type -
`IsInstance[Foo]` is exactly equivalent to `IsInstance(Foo)`.
This allows usage to be analogous to type hints.
Example:
```py title="IsInstance"
from dirty_equals import IsInstance
class Foo:
pass
class Bar(Foo):
pass
assert Foo() == IsInstance[Foo]
assert Foo() == IsInstance(Foo)
assert Foo != IsInstance[Bar]
assert Bar() == IsInstance[Foo]
assert Foo() == IsInstance(Foo, only_direct_instance=True)
assert Bar() != IsInstance(Foo, only_direct_instance=True)
```
"""
self.expected_type = expected_type
self.only_direct_instance = only_direct_instance
super().__init__(expected_type)
def __class_getitem__(cls, expected_type: ExpectedType) -> 'IsInstance[ExpectedType]':
return cls(expected_type)
def equals(self, other: Any) -> bool:
if self.only_direct_instance:
return type(other) == self.expected_type
else:
return isinstance(other, self.expected_type)
T = TypeVar('T')
class HasName(DirtyEquals[T]):
"""
A type which checks that the value has the given `__name__` attribute.
"""
def __init__(self, expected_name: Union[IsStr, str], *, allow_instances: bool = True):
"""
Args:
expected_name: The name to check against.
allow_instances: whether instances of classes with the given name should be considered equal,
(e.g. whether `other.__class__.__name__ == expected_name` should be checked).
Example:
```py title="HasName"
from dirty_equals import HasName, IsStr
class Foo:
pass
assert Foo == HasName('Foo')
assert Foo == HasName['Foo']
assert Foo() == HasName('Foo')
assert Foo() != HasName('Foo', allow_instances=False)
assert Foo == HasName(IsStr(regex='F..'))
assert Foo != HasName('Bar')
assert int == HasName('int')
assert int == HasName('int')
```
"""
self.expected_name = expected_name
self.allow_instances = allow_instances
kwargs = {}
if allow_instances:
kwargs['allow_instances'] = allow_instances
super().__init__(expected_name, allow_instances=allow_instances)
def __class_getitem__(cls, expected_name: str) -> 'HasName[T]':
return cls(expected_name)
def equals(self, other: Any) -> bool:
direct_name = getattr(other, '__name__', None)
if direct_name is not None and direct_name == self.expected_name:
return True
if self.allow_instances:
cls = getattr(other, '__class__', None)
if cls is not None: # pragma: no branch
cls_name = getattr(cls, '__name__', None)
if cls_name is not None and cls_name == self.expected_name:
return True
return False
class HasRepr(DirtyEquals[T]):
"""
A type which checks that the value has the given `repr()` value.
"""
def __init__(self, expected_repr: Union[IsStr, str]):
"""
Args:
expected_repr: The expected repr value.
Example:
```py title="HasRepr"
from dirty_equals import HasRepr, IsStr
class Foo:
def __repr__(self):
return 'This is a Foo'
assert Foo() == HasRepr('This is a Foo')
assert Foo() == HasRepr['This is a Foo']
assert Foo == HasRepr(IsStr(regex=' 'HasRepr[T]':
return cls(expected_repr)
def equals(self, other: Any) -> bool:
return repr(other) == self.expected_repr
class HasAttributes(DirtyEquals[Any]):
"""
A type which checks that the value has the given attributes.
This is a partial check - e.g. the attributes provided to check do not need to be exhaustive.
"""
@overload
def __init__(self, expected: dict[Any, Any]): ...
@overload
def __init__(self, **expected: Any): ...
def __init__(self, *expected_args: dict[Any, Any], **expected_kwargs: Any):
"""
Can be created from either keyword arguments or an existing dictionary (same as `dict()`).
Example:
```py title="HasAttributes"
from dirty_equals import AnyThing, HasAttributes, IsInt, IsStr
class Foo:
def __init__(self, a, b):
self.a = a
self.b = b
def spam(self):
pass
assert Foo(1, 2) == HasAttributes(a=1, b=2)
assert Foo(1, 2) == HasAttributes(a=1)
assert Foo(1, 's') == HasAttributes(a=IsInt, b=IsStr)
assert Foo(1, 2) != HasAttributes(a=IsInt, b=IsStr)
assert Foo(1, 2) != HasAttributes(a=1, b=2, c=3)
assert Foo(1, 2) == HasAttributes(a=1, b=2, spam=AnyThing)
```
"""
self.expected_attrs = get_dict_arg('HasAttributes', expected_args, expected_kwargs)
super().__init__(**self.expected_attrs)
def equals(self, other: Any) -> bool:
for attr, expected_value in self.expected_attrs.items():
# done like this to avoid problems with `AnyThing` equaling `None` or `DefaultAttr`
try:
value = getattr(other, attr)
except AttributeError:
return False
else:
if value != expected_value:
return False
return True
dirty-equals-0.10.0/dirty_equals/_numeric.py 0000664 0000000 0000000 00000033430 15063277303 0021104 0 ustar 00root root 0000000 0000000 import math
from datetime import date, datetime, timedelta
from decimal import Decimal
from typing import Any, Optional, TypeVar, Union
from ._base import DirtyEquals
__all__ = (
'IsApprox',
'IsNumeric',
'IsNumber',
'IsPositive',
'IsNegative',
'IsNonPositive',
'IsNonNegative',
'IsInt',
'IsPositiveInt',
'IsNegativeInt',
'IsFloat',
'IsPositiveFloat',
'IsNegativeFloat',
)
from ._utils import Omit
AnyNumber = Union[int, float, Decimal]
N = TypeVar('N', int, float, Decimal, date, datetime, AnyNumber)
class IsNumeric(DirtyEquals[N]):
"""
Base class for all numeric types, `IsNumeric` implements approximate and inequality comparisons,
as well as the type checks.
This class can be used directly or via any of its subclasses.
"""
allowed_types: Union[type[N], tuple[type, ...]] = (int, float, Decimal, date, datetime)
"""It allows any of the types supported in its subclasses."""
def __init__(
self,
*,
exactly: Optional[N] = None,
approx: Optional[N] = None,
delta: Optional[N] = None,
gt: Optional[N] = None,
lt: Optional[N] = None,
ge: Optional[N] = None,
le: Optional[N] = None,
):
"""
Args:
exactly: A value to exactly compare to - useful when you want to make sure a value is an `int` or `float`,
while also checking its value.
approx: A value to approximately compare to.
delta: The allowable different when comparing to the value to `approx`,
if omitted `value / 100` is used except for datetimes where 2 seconds is used.
gt: Value which the compared value should be greater than.
lt: Value which the compared value should be less than.
ge: Value which the compared value should be greater than or equal to.
le: Value which the compared value should be less than or equal to.
If not values are provided, only the type is checked.
If `approx` is provided as well a `gt`, `lt`, `ge`, or `le`, a `TypeError` is raised.
Example of direct usage:
```py title="IsNumeric"
from datetime import datetime
from dirty_equals import IsNumeric
assert 1.0 == IsNumeric
assert 4 == IsNumeric(gt=3)
d = datetime(2020, 1, 1, 12, 0, 0)
assert d == IsNumeric(approx=datetime(2020, 1, 1, 12, 0, 1))
```
"""
self.exactly: Optional[N] = exactly
if self.exactly is not None and (gt, lt, ge, le) != (None, None, None, None):
raise TypeError('"exactly" cannot be combined with "gt", "lt", "ge", or "le"')
if self.exactly is not None and approx is not None:
raise TypeError('"exactly" cannot be combined with "approx"')
self.approx: Optional[N] = approx
if self.approx is not None and (gt, lt, ge, le) != (None, None, None, None):
raise TypeError('"approx" cannot be combined with "gt", "lt", "ge", or "le"')
self.delta: Optional[N] = delta
self.gt: Optional[N] = gt
self.lt: Optional[N] = lt
self.ge: Optional[N] = ge
self.le: Optional[N] = le
self.has_bounds_checks = not all(f is None for f in (exactly, approx, delta, gt, lt, ge, le))
kwargs = {
'exactly': Omit if exactly is None else exactly,
'approx': Omit if approx is None else approx,
'delta': Omit if delta is None else delta,
'gt': Omit if gt is None else gt,
'lt': Omit if lt is None else lt,
'ge': Omit if ge is None else ge,
'le': Omit if le is None else le,
}
super().__init__(**kwargs)
def prepare(self, other: Any) -> N:
if other is True or other is False:
raise TypeError('booleans are not numbers')
elif not isinstance(other, self.allowed_types):
raise TypeError(f'not a {self.allowed_types}')
else:
return other
def equals(self, other: Any) -> bool:
other = self.prepare(other)
if self.has_bounds_checks:
return self.bounds_checks(other)
else:
return True
def bounds_checks(self, other: N) -> bool:
if self.exactly is not None:
return self.exactly == other
elif self.approx is not None:
if self.delta is None:
if isinstance(other, date):
delta: Any = timedelta(seconds=2)
else:
delta = abs(other / 100)
else:
delta = self.delta
return self.approx_equals(other, delta)
elif self.gt is not None and not other > self.gt:
return False
elif self.lt is not None and not other < self.lt:
return False
elif self.ge is not None and not other >= self.ge:
return False
elif self.le is not None and not other <= self.le:
return False
else:
return True
def approx_equals(self, other: Any, delta: Any) -> bool:
return abs(self.approx - other) <= delta
class IsNumber(IsNumeric[AnyNumber]):
"""
Base class for all types that can be used with all number types, e.g. numeric but not `date` or `datetime`.
Inherits from [`IsNumeric`][dirty_equals.IsNumeric] and can therefore be initialised with any of its arguments.
"""
allowed_types = int, float, Decimal
"""
It allows any of the number types.
"""
Num = TypeVar('Num', int, float, Decimal)
class IsApprox(IsNumber):
"""
Simplified subclass of [`IsNumber`][dirty_equals.IsNumber] that only allows approximate comparisons.
"""
def __init__(self, approx: Num, *, delta: Optional[Num] = None):
"""
Args:
approx: A value to approximately compare to.
delta: The allowable different when comparing to the value to `approx`, if omitted `value / 100` is used.
```py title="IsApprox"
from dirty_equals import IsApprox
assert 1.0 == IsApprox(1)
assert 123 == IsApprox(120, delta=4)
assert 201 == IsApprox(200)
assert 201 != IsApprox(200, delta=0.1)
```
"""
super().__init__(approx=approx, delta=delta)
class IsPositive(IsNumber):
"""
Check that a value is positive (`> 0`), can be an `int`, a `float` or a `Decimal`
(or indeed any value which implements `__gt__` for `0`).
```py title="IsPositive"
from decimal import Decimal
from dirty_equals import IsPositive
assert 1.0 == IsPositive
assert 1 == IsPositive
assert Decimal('3.14') == IsPositive
assert 0 != IsPositive
assert -1 != IsPositive
```
"""
def __init__(self) -> None:
super().__init__(gt=0)
self._repr_kwargs = {}
class IsNegative(IsNumber):
"""
Check that a value is negative (`< 0`), can be an `int`, a `float` or a `Decimal`
(or indeed any value which implements `__lt__` for `0`).
```py title="IsNegative"
from decimal import Decimal
from dirty_equals import IsNegative
assert -1.0 == IsNegative
assert -1 == IsNegative
assert Decimal('-3.14') == IsNegative
assert 0 != IsNegative
assert 1 != IsNegative
```
"""
def __init__(self) -> None:
super().__init__(lt=0)
self._repr_kwargs = {}
class IsNonNegative(IsNumber):
"""
Check that a value is positive or zero (`>= 0`), can be an `int`, a `float` or a `Decimal`
(or indeed any value which implements `__ge__` for `0`).
```py title="IsNonNegative"
from decimal import Decimal
from dirty_equals import IsNonNegative
assert 1.0 == IsNonNegative
assert 1 == IsNonNegative
assert Decimal('3.14') == IsNonNegative
assert 0 == IsNonNegative
assert -1 != IsNonNegative
assert Decimal('0') == IsNonNegative
```
"""
def __init__(self) -> None:
super().__init__(ge=0)
self._repr_kwargs = {}
class IsNonPositive(IsNumber):
"""
Check that a value is negative or zero (`<=0`), can be an `int`, a `float` or a `Decimal`
(or indeed any value which implements `__le__` for `0`).
```py title="IsNonPositive"
from decimal import Decimal
from dirty_equals import IsNonPositive
assert -1.0 == IsNonPositive
assert -1 == IsNonPositive
assert Decimal('-3.14') == IsNonPositive
assert 0 == IsNonPositive
assert 1 != IsNonPositive
assert Decimal('-0') == IsNonPositive
assert Decimal('0') == IsNonPositive
```
"""
def __init__(self) -> None:
super().__init__(le=0)
self._repr_kwargs = {}
class IsInt(IsNumeric[int]):
"""
Checks that a value is an integer.
Inherits from [`IsNumeric`][dirty_equals.IsNumeric] and can therefore be initialised with any of its arguments.
```py title="IsInt"
from dirty_equals import IsInt
assert 1 == IsInt
assert -2 == IsInt
assert 1.0 != IsInt
assert 'foobar' != IsInt
assert True != IsInt
assert 1 == IsInt(exactly=1)
assert -2 != IsInt(exactly=1)
```
"""
allowed_types = int
"""
As the name suggests, only integers are allowed, booleans (`True` are `False`) are explicitly excluded although
technically they are sub-types of `int`.
"""
class IsPositiveInt(IsInt):
"""
Like [`IsPositive`][dirty_equals.IsPositive] but only for `int`s.
```py title="IsPositiveInt"
from decimal import Decimal
from dirty_equals import IsPositiveInt
assert 1 == IsPositiveInt
assert 1.0 != IsPositiveInt
assert Decimal('3.14') != IsPositiveInt
assert 0 != IsPositiveInt
assert -1 != IsPositiveInt
```
"""
def __init__(self) -> None:
super().__init__(gt=0)
self._repr_kwargs = {}
class IsNegativeInt(IsInt):
"""
Like [`IsNegative`][dirty_equals.IsNegative] but only for `int`s.
```py title="IsNegativeInt"
from decimal import Decimal
from dirty_equals import IsNegativeInt
assert -1 == IsNegativeInt
assert -1.0 != IsNegativeInt
assert Decimal('-3.14') != IsNegativeInt
assert 0 != IsNegativeInt
assert 1 != IsNegativeInt
```
"""
def __init__(self) -> None:
super().__init__(lt=0)
self._repr_kwargs = {}
class IsFloat(IsNumeric[float]):
"""
Checks that a value is a float.
Inherits from [`IsNumeric`][dirty_equals.IsNumeric] and can therefore be initialised with any of its arguments.
```py title="IsFloat"
from dirty_equals import IsFloat
assert 1.0 == IsFloat
assert 1 != IsFloat
assert 1.0 == IsFloat(exactly=1.0)
assert 1.001 != IsFloat(exactly=1.0)
```
"""
allowed_types = float
"""
As the name suggests, only floats are allowed.
"""
class IsPositiveFloat(IsFloat):
"""
Like [`IsPositive`][dirty_equals.IsPositive] but only for `float`s.
```py title="IsPositiveFloat"
from decimal import Decimal
from dirty_equals import IsPositiveFloat
assert 1.0 == IsPositiveFloat
assert 1 != IsPositiveFloat
assert Decimal('3.14') != IsPositiveFloat
assert 0.0 != IsPositiveFloat
assert -1.0 != IsPositiveFloat
```
"""
def __init__(self) -> None:
super().__init__(gt=0)
self._repr_kwargs = {}
class IsNegativeFloat(IsFloat):
"""
Like [`IsNegative`][dirty_equals.IsNegative] but only for `float`s.
```py title="IsNegativeFloat"
from decimal import Decimal
from dirty_equals import IsNegativeFloat
assert -1.0 == IsNegativeFloat
assert -1 != IsNegativeFloat
assert Decimal('-3.14') != IsNegativeFloat
assert 0.0 != IsNegativeFloat
assert 1.0 != IsNegativeFloat
```
"""
def __init__(self) -> None:
super().__init__(lt=0)
self._repr_kwargs = {}
class IsFloatInf(IsFloat):
"""
Checks that a value is float and infinite (positive or negative).
Inherits from [`IsFloat`][dirty_equals.IsFloat].
```py title="IsFloatInf"
from dirty_equals import IsFloatInf
assert float('inf') == IsFloatInf
assert float('-inf') == IsFloatInf
assert 1.0 != IsFloatInf
```
"""
def equals(self, other: Any) -> bool:
other = self.prepare(other)
return math.isinf(other)
class IsFloatInfPos(IsFloatInf):
"""
Checks that a value is float and positive infinite.
Inherits from [`IsFloatInf`][dirty_equals.IsFloatInf].
```py title="IsFloatInfPos"
from dirty_equals import IsFloatInfPos
assert float('inf') == IsFloatInfPos
assert -float('-inf') == IsFloatInfPos
assert -float('inf') != IsFloatInfPos
assert float('-inf') != IsFloatInfPos
```
"""
def __init__(self) -> None:
super().__init__(gt=0)
self._repr_kwargs = {}
def equals(self, other: Any) -> bool:
return self.bounds_checks(other) and super().equals(other)
class IsFloatInfNeg(IsFloatInf):
"""
Checks that a value is float and negative infinite.
Inherits from [`IsFloatInf`][dirty_equals.IsFloatInf].
```py title="IsFloatInfNeg"
from dirty_equals import IsFloatInfNeg
assert -float('inf') == IsFloatInfNeg
assert float('-inf') == IsFloatInfNeg
assert float('inf') != IsFloatInfNeg
assert -float('-inf') != IsFloatInfNeg
```
"""
def __init__(self) -> None:
super().__init__(lt=0)
self._repr_kwargs = {}
def equals(self, other: Any) -> bool:
return self.bounds_checks(other) and super().equals(other)
class IsFloatNan(IsFloat):
"""
Checks that a value is float and nan (not a number).
Inherits from [`IsFloat`][dirty_equals.IsFloat].
```py title="IsFloatNan"
from dirty_equals import IsFloatNan
assert float('nan') == IsFloatNan
assert 1.0 != IsFloatNan
```
"""
def equals(self, other: Any) -> bool:
other = self.prepare(other)
return math.isnan(other)
dirty-equals-0.10.0/dirty_equals/_other.py 0000664 0000000 0000000 00000044737 15063277303 0020577 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import json
import re
from dataclasses import asdict, is_dataclass
from enum import Enum
from functools import lru_cache
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, Union, overload
from uuid import UUID
from ._base import DirtyEquals
from ._dict import IsDict
from ._utils import Omit, plain_repr
if TYPE_CHECKING:
from pydantic import TypeAdapter
class IsUUID(DirtyEquals[UUID]):
"""
A class that checks if a value is a valid UUID, optionally checking UUID version.
"""
def __init__(self, version: Literal[None, 1, 2, 3, 4, 5] = None):
"""
Args:
version: The version of the UUID to check, if omitted, all versions are accepted.
```py title="IsUUID"
import uuid
from dirty_equals import IsUUID
assert 'edf9f29e-45c7-431c-99db-28ea44df9785' == IsUUID
assert 'edf9f29e-45c7-431c-99db-28ea44df9785' == IsUUID(4)
assert 'edf9f29e45c7431c99db28ea44df9785' == IsUUID(4)
assert 'edf9f29e-45c7-431c-99db-28ea44df9785' != IsUUID(5)
assert uuid.uuid4() == IsUUID(4)
```
"""
self.version = version
super().__init__(version or plain_repr(''))
def equals(self, other: Any) -> bool:
if isinstance(other, UUID):
uuid = other
elif isinstance(other, str):
uuid = UUID(other)
if self.version is not None and uuid.version != self.version:
return False
else:
return False
if self.version:
return uuid.version == self.version
else:
return True
AnyJson = object
JsonType = TypeVar('JsonType', AnyJson, Any)
class IsJson(DirtyEquals[JsonType]):
"""
A class that checks if a value is a JSON object, and check the contents of the JSON.
"""
@overload
def __init__(self, expected_value: JsonType = AnyJson): ...
@overload
def __init__(self, **expected_kwargs: Any): ...
def __init__(self, expected_value: JsonType = AnyJson, **expected_kwargs: Any):
"""
Args:
expected_value: Value to compare the JSON to, if omitted, any JSON is accepted.
**expected_kwargs: Keyword arguments forming a dict to compare the JSON to,
`expected_value` and `expected_kwargs` may not be combined.
As with any `dirty_equals` type, types can be nested to provide more complex checks.
!!! note
Like [`IsInstance`][dirty_equals.IsInstance], `IsJson` can be parameterized or initialised with a value -
`IsJson[xyz]` is exactly equivalent to `IsJson(xyz)`.
This allows usage to be analogous to type hints.
```py title="IsJson"
from dirty_equals import IsJson, IsPositiveInt, IsStrictDict
assert '{"a": 1, "b": 2}' == IsJson
assert '{"a": 1, "b": 2}' == IsJson(a=1, b=2)
assert '{"a": 1}' != IsJson(a=2)
assert 'invalid json' != IsJson
assert '{"a": 1}' == IsJson(a=IsPositiveInt)
assert '"just a quoted string"' == IsJson('just a quoted string')
assert '{"a": 1, "b": 2}' == IsJson[IsStrictDict(a=1, b=2)]
assert '{"b": 2, "a": 1}' != IsJson[IsStrictDict(a=1, b=2)]
```
"""
if expected_kwargs:
if expected_value is not AnyJson:
raise TypeError('IsJson requires either an argument or kwargs, not both')
self.expected_value: Any = expected_kwargs
else:
self.expected_value = expected_value
super().__init__(plain_repr('') if expected_value is AnyJson else expected_value)
def __class_getitem__(cls, expected_type: JsonType) -> IsJson[JsonType]:
return cls(expected_type)
def equals(self, other: Any) -> bool:
if isinstance(other, (str, bytes)):
v = json.loads(other)
if self.expected_value is AnyJson:
return True
else:
return v == self.expected_value
else:
return False
class FunctionCheck(DirtyEquals[Any]):
"""
Use a function to check if a value "equals" whatever you want to check
"""
def __init__(self, func: Callable[[Any], bool]):
"""
Args:
func: callable that takes a value and returns a bool.
```py title="FunctionCheck"
from dirty_equals import FunctionCheck
def is_even(x):
return x % 2 == 0
assert 2 == FunctionCheck(is_even)
assert 3 != FunctionCheck(is_even)
```
"""
self.func = func
super().__init__(plain_repr(func.__name__))
def equals(self, other: Any) -> bool:
return self.func(other)
T = TypeVar('T')
@lru_cache
def _build_type_adapter(ta: type[TypeAdapter[T]], schema: T) -> TypeAdapter[T]:
return ta(schema)
_allowed_url_attribute_checks: set[str] = {
'scheme',
'host',
'host_type',
'user',
'password',
'port',
'path',
'query',
'fragment',
}
class IsUrl(DirtyEquals[Any]):
"""
A class that checks if a value is a valid URL, optionally checking different URL types and attributes with
[Pydantic](https://pydantic-docs.helpmanual.io/usage/types/#urls).
"""
def __init__(
self,
any_url: bool = False,
any_http_url: bool = False,
http_url: bool = False,
file_url: bool = False,
postgres_dsn: bool = False,
ampqp_dsn: bool = False,
redis_dsn: bool = False,
**expected_attributes: Any,
):
"""
Args:
any_url: any scheme allowed, host required
any_http_url: scheme http or https, host required
http_url: scheme http or https, host required, max length 2083
file_url: scheme file, host not required
postgres_dsn: user info required
ampqp_dsn: schema amqp or amqps, user info not required, host not required
redis_dsn: scheme redis or rediss, user info not required, host not required
**expected_attributes: Expected values for url attributes
```py title="IsUrl"
from dirty_equals import IsUrl
assert 'https://example.com' == IsUrl
assert 'https://example.com' == IsUrl(host='example.com')
assert 'https://example.com' == IsUrl(scheme='https')
assert 'https://example.com' != IsUrl(scheme='http')
assert 'postgres://user:pass@localhost:5432/app' == IsUrl(postgres_dsn=True)
assert 'postgres://user:pass@localhost:5432/app' != IsUrl(http_url=True)
```
"""
try:
from pydantic import (
AmqpDsn,
AnyHttpUrl,
AnyUrl,
FileUrl,
HttpUrl,
PostgresDsn,
RedisDsn,
TypeAdapter,
ValidationError,
)
self.ValidationError = ValidationError
except ImportError as e: # pragma: no cover
raise ImportError('Pydantic V2 is not installed, run `pip install dirty-equals[pydantic]`') from e
url_type_mappings = {
AnyUrl: any_url,
AnyHttpUrl: any_http_url,
HttpUrl: http_url,
FileUrl: file_url,
PostgresDsn: postgres_dsn,
AmqpDsn: ampqp_dsn,
RedisDsn: redis_dsn,
}
url_types_sum = sum(url_type_mappings.values())
if url_types_sum == 0:
url_type: Any = AnyUrl
elif url_types_sum == 1:
url_type = max(url_type_mappings, key=url_type_mappings.get) # type: ignore[arg-type]
else:
raise ValueError('You can only check against one Pydantic url type at a time')
self.type_adapter = _build_type_adapter(TypeAdapter, url_type)
for item in expected_attributes:
if item not in _allowed_url_attribute_checks:
raise TypeError(
'IsURL only checks these attributes: scheme, host, host_type, user, password, '
'port, path, query, fragment'
)
self.attribute_checks = expected_attributes
super().__init__()
def equals(self, other: Any) -> bool:
try:
other_url = self.type_adapter.validate_python(other)
except self.ValidationError:
raise ValueError('Invalid URL')
# we now check that str() of the parsed URL equals its original value
# so that invalid encodings fail
# we remove trailing slashes since they're added by pydantic's URL parsing, but don't mean `other` is invalid
other_url_str = str(other_url)
if not other.endswith('/') and other_url_str.endswith('/'):
other_url_str = other_url_str[:-1]
equal = other_url_str == other
if not self.attribute_checks:
return equal
for attribute, expected in self.attribute_checks.items():
if getattr(other_url, attribute) != expected:
return False
return equal
HashTypes = Literal['md5', 'sha-1', 'sha-256']
class IsHash(DirtyEquals[str]):
"""
A class that checks if a value is a valid common hash type, using a simple length and allowed characters regex.
"""
def __init__(self, hash_type: HashTypes):
"""
Args:
hash_type: The hash type to check. Must be specified.
```py title="IsHash"
from dirty_equals import IsHash
assert 'f1e069787ece74531d112559945c6871' == IsHash('md5')
assert b'f1e069787ece74531d112559945c6871' == IsHash('md5')
assert 'f1e069787ece74531d112559945c6871' != IsHash('sha-256')
assert 'F1E069787ECE74531D112559945C6871' == IsHash('md5')
assert '40bd001563085fc35165329ea1ff5c5ecbdbbeef' == IsHash('sha-1')
assert 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3' == IsHash(
'sha-256'
)
```
"""
allowed_hashes = HashTypes.__args__ # type: ignore[attr-defined]
if hash_type not in allowed_hashes:
raise ValueError(f'Hash type must be one of the following values: {", ".join(allowed_hashes)}')
self.hash_type = hash_type
super().__init__(hash_type)
def equals(self, other: Any) -> bool:
if isinstance(other, str):
s = other
elif isinstance(other, (bytes, bytearray)):
s = other.decode()
else:
return False
hash_type_regex_patterns = {
'md5': r'[a-fA-F\d]{32}',
'sha-1': r'[a-fA-F\d]{40}',
'sha-256': r'[a-fA-F\d]{64}',
}
return bool(re.fullmatch(hash_type_regex_patterns[self.hash_type], s))
IP = TypeVar('IP', IPv4Address, IPv4Network, IPv6Address, IPv6Network, Union[str, int, bytes])
class IsIP(DirtyEquals[IP]):
"""
A class that checks if a value is a valid IP address, optionally checking IP version, netmask.
"""
def __init__(self, *, version: Literal[None, 4, 6] = None, netmask: str | None = None):
"""
Args:
version: The version of the IP to check, if omitted, versions 4 and 6 are both accepted.
netmask: The netmask of the IP to check, if omitted, any netmask is accepted. Requires version.
```py title="IsIP"
from ipaddress import IPv4Address, IPv4Network, IPv6Address
from dirty_equals import IsIP
assert '179.27.154.96' == IsIP
assert '179.27.154.96' == IsIP(version=4)
assert '2001:0db8:0a0b:12f0:0000:0000:0000:0001' == IsIP(version=6)
assert IPv4Address('127.0.0.1') == IsIP
assert IPv4Network('43.48.0.0/12') == IsIP
assert IPv6Address('::eeff:ae3f:d473') == IsIP
assert '54.43.53.219/10' == IsIP(version=4, netmask='255.192.0.0')
assert '54.43.53.219/10' == IsIP(version=4, netmask=4290772992)
assert '::ffff:aebf:d473/12' == IsIP(version=6, netmask='fff0::')
assert 3232235521 == IsIP
```
"""
self.version = version
if netmask and not self.version:
raise TypeError('To check the netmask you must specify the IP version')
self.netmask = netmask
super().__init__(version=version or Omit, netmask=netmask or Omit)
def equals(self, other: Any) -> bool:
if isinstance(other, (IPv4Network, IPv6Network)):
ip = other
elif isinstance(other, (str, bytes, int, IPv4Address, IPv6Address)):
ip = ip_network(other, strict=False)
else:
return False
if self.version:
if self.netmask:
version_check = self.version == ip.version
address_format = {4: IPv4Address, 6: IPv6Address}[self.version]
netmask_check = int(address_format(self.netmask)) == int(ip.netmask)
return version_check and netmask_check
elif self.version != ip.version:
return False
return True
class IsDataclassType(DirtyEquals[Any]):
"""
Checks that an object is a dataclass type.
Inherits from [`DirtyEquals`][dirty_equals.DirtyEquals].
```py title="IsDataclassType"
from dataclasses import dataclass
from dirty_equals import IsDataclassType
@dataclass
class Foo:
a: int
b: int
foo = Foo(1, 2)
assert Foo == IsDataclassType
assert foo != IsDataclassType
```
"""
def equals(self, other: Any) -> bool:
return is_dataclass(other) and isinstance(other, type)
class IsDataclass(DirtyEquals[Any]):
"""
Checks that an object is an instance of a dataclass.
Inherits from [`DirtyEquals`][dirty_equals.DirtyEquals] and it can be initialised with specific keyword arguments to
check exactness of dataclass fields, by comparing the instance `__dict__` with [`IsDict`][dirty_equals.IsDict].
Moreover it is possible to check for strictness and partialness of the dataclass, by setting the `strict` and
`partial` attributes using the `.settings(strict=..., partial=...)` method.
Remark that passing no kwargs to `IsDataclass` initialization means fields are not checked, not that the dataclass
is empty, namely `IsDataclass()` is the same as `IsDataclass`.
```py title="IsDataclass"
from dataclasses import dataclass
from dirty_equals import IsInt, IsDataclass
@dataclass
class Foo:
a: int
b: int
c: str
foo = Foo(1, 2, 'c')
assert foo == IsDataclass
assert foo == IsDataclass(a=IsInt, b=2, c='c')
assert foo == IsDataclass(b=2, a=1).settings(partial=True)
assert foo != IsDataclass(a=IsInt, b=2).settings(strict=True)
assert foo == IsDataclass(a=IsInt, b=2).settings(strict=True, partial=True)
assert foo != IsDataclass(b=2, a=1).settings(strict=True, partial=True)
```
"""
def __init__(self, **fields: Any):
"""
Args:
fields: key-value pairs of field-value to check for.
"""
self.strict = False
self.partial = False
self._post_init()
super().__init__(**fields)
def _post_init(self) -> None:
pass
def equals(self, other: Any) -> bool:
if is_dataclass(other) and not isinstance(other, type):
if self._repr_kwargs:
return self._fields_check(other)
else:
return True
else:
return False
def settings(
self,
*,
strict: bool | None = None,
partial: bool | None = None,
) -> IsDataclass:
"""Allows to customise the behaviour of `IsDataclass`, technically a new `IsDataclass` to allow chaining."""
new_cls = self.__class__(**self._repr_kwargs)
new_cls.__dict__ = self.__dict__.copy()
if strict is not None:
new_cls.strict = strict
if partial is not None:
new_cls.partial = partial
return new_cls
def _fields_check(self, other: Any) -> bool:
"""
Checks exactness of fields using [`IsDict`][dirty_equals.IsDict] with given settings.
Remark that if this method is called, then `other` is an instance of a dataclass, therefore we can call
`dataclasses.asdict` to convert to a dict.
"""
return asdict(other) == IsDict(self._repr_kwargs).settings(strict=self.strict, partial=self.partial)
class IsPartialDataclass(IsDataclass):
"""
Inherits from [`IsDataclass`][dirty_equals.IsDataclass] with `partial=True` by default.
```py title="IsPartialDataclass"
from dataclasses import dataclass
from dirty_equals import IsInt, IsPartialDataclass
@dataclass
class Foo:
a: int
b: int
c: str = 'c'
foo = Foo(1, 2, 'c')
assert foo == IsPartialDataclass
assert foo == IsPartialDataclass(a=1)
assert foo == IsPartialDataclass(b=2, a=IsInt)
assert foo != IsPartialDataclass(b=2, a=IsInt).settings(strict=True)
assert Foo != IsPartialDataclass
```
"""
def _post_init(self) -> None:
self.partial = True
class IsStrictDataclass(IsDataclass):
"""
Inherits from [`IsDataclass`][dirty_equals.IsDataclass] with `strict=True` by default.
```py title="IsStrictDataclass"
from dataclasses import dataclass
from dirty_equals import IsInt, IsStrictDataclass
@dataclass
class Foo:
a: int
b: int
c: str = 'c'
foo = Foo(1, 2, 'c')
assert foo == IsStrictDataclass
assert foo == IsStrictDataclass(
a=IsInt,
b=2,
).settings(partial=True)
assert foo != IsStrictDataclass(
a=IsInt,
b=2,
).settings(partial=False)
assert foo != IsStrictDataclass(b=2, a=IsInt, c='c')
```
"""
def _post_init(self) -> None:
self.strict = True
class IsEnum(DirtyEquals[Enum]):
"""
Checks if an instance is an Enum.
Inherits from [`DirtyEquals`][dirty_equals.DirtyEquals].
```py title="IsEnum"
from enum import Enum, auto
from dirty_equals import IsEnum
class ExampleEnum(Enum):
a = auto()
b = auto()
a = ExampleEnum.a
assert a == IsEnum
assert a == IsEnum(ExampleEnum)
```
"""
def __init__(self, enum_cls: type[Enum] = Enum):
"""
Args:
enum_cls: Enum class to check against.
"""
self._enum_cls = enum_cls
self._enum_values = {i.value for i in enum_cls}
def equals(self, other: Any) -> bool:
if isinstance(other, Enum):
return isinstance(other, self._enum_cls)
else:
return other in self._enum_values
dirty-equals-0.10.0/dirty_equals/_sequence.py 0000664 0000000 0000000 00000023754 15063277303 0021262 0 ustar 00root root 0000000 0000000 import sys
from collections.abc import Container, Sized
from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union, overload
from ._base import DirtyEquals
from ._utils import Omit, plain_repr
if TYPE_CHECKING:
from typing import TypeAlias
if sys.version_info >= (3, 10):
from types import EllipsisType
else:
EllipsisType = Any
__all__ = 'HasLen', 'Contains', 'IsListOrTuple', 'IsList', 'IsTuple'
T = TypeVar('T', list[Any], tuple[Any, ...])
LengthType: 'TypeAlias' = 'Union[None, int, tuple[int, Union[int, Any]], EllipsisType]'
class HasLen(DirtyEquals[Sized]):
"""
Check that some has a given length, or length in a given range.
"""
@overload
def __init__(self, length: int): ...
@overload
def __init__(self, min_length: int, max_length: Union[int, Any]): ...
def __init__(self, min_length: int, max_length: Union[None, int, Any] = None): # type: ignore[misc]
"""
Args:
min_length: Expected length if `max_length` is not given, else minimum length.
max_length: Expected maximum length, use an ellipsis `...` to indicate that there's no maximum.
```py title="HasLen"
from dirty_equals import HasLen
assert [1, 2, 3] == HasLen(3) # (1)!
assert '123' == HasLen(3, ...) # (2)!
assert (1, 2, 3) == HasLen(3, 5) # (3)!
assert (1, 2, 3) == HasLen(0, ...) # (4)!
```
1. Length must be 3.
2. Length must be 3 or higher.
3. Length must be between 3 and 5 inclusive.
4. Length is required but can take any value.
"""
if max_length is None:
self.length: LengthType = min_length
super().__init__(self.length)
else:
self.length = (min_length, max_length)
super().__init__(*_length_repr(self.length))
def equals(self, other: Any) -> bool:
return _length_correct(self.length, other)
class Contains(DirtyEquals[Container[Any]]):
"""
Check that an object contains one or more values.
"""
def __init__(self, contained_value: Any, *more_contained_values: Any):
"""
Args:
contained_value: value that must be contained in the compared object.
*more_contained_values: more values that must be contained in the compared object.
```py title="Contains"
from dirty_equals import Contains
assert [1, 2, 3] == Contains(1)
assert [1, 2, 3] == Contains(1, 2)
assert (1, 2, 3) == Contains(1)
assert 'abc' == Contains('b')
assert {'a': 1, 'b': 2} == Contains('a')
assert [1, 2, 3] != Contains(10)
```
"""
self.contained_values: tuple[Any, ...] = (contained_value,) + more_contained_values
super().__init__(*self.contained_values)
def equals(self, other: Any) -> bool:
return all(v in other for v in self.contained_values)
class IsListOrTuple(DirtyEquals[T]):
"""
Check that some object is a list or tuple and optionally its values match some constraints.
"""
allowed_type: Union[type[T], tuple[type[list[Any]], type[tuple[Any, ...]]]] = (list, tuple)
@overload
def __init__(self, *items: Any, check_order: bool = True, length: 'LengthType' = None): ...
@overload
def __init__(self, positions: dict[int, Any], length: 'LengthType' = None): ...
def __init__(
self,
*items: Any,
positions: Optional[dict[int, Any]] = None,
check_order: bool = True,
length: 'LengthType' = None,
):
"""
`IsListOrTuple` and its subclasses can be initialised in two ways:
Args:
*items: Positional members of an object to check. These must start from the zeroth position, but
(depending on the value of `length`) may not include all values of the list/tuple being checked.
check_order: Whether to enforce the order of the items.
length (Union[int, tuple[int, Union[int, Any]]]): length constraints, int or tuple matching the arguments
of [`HasLen`][dirty_equals.HasLen].
or,
Args:
positions (dict[int, Any]): Instead of `*items`, a dictionary of positions and
values to check and be provided.
length (Union[int, tuple[int, Union[int, Any]]]): length constraints, int or tuple matching the arguments
of [`HasLen`][dirty_equals.HasLen].
```py title="IsListOrTuple"
from dirty_equals import AnyThing, IsListOrTuple
assert [1, 2, 3] == IsListOrTuple(1, 2, 3)
assert (1, 3, 2) == IsListOrTuple(1, 2, 3, check_order=False)
assert [{'a': 1}, {'a': 2}] == (
IsListOrTuple({'a': 2}, {'a': 1}, check_order=False) # (1)!
)
assert [1, 2, 3, 3] != IsListOrTuple(1, 2, 3, check_order=False) # (2)!
assert [1, 2, 3, 4, 5] == IsListOrTuple(1, 2, 3, length=...) # (3)!
assert [1, 2, 3, 4, 5] != IsListOrTuple(1, 2, 3, length=(8, 10)) # (4)!
assert ['a', 'b', 'c', 'd'] == (IsListOrTuple(positions={2: 'c', 3: 'd'})) # (5)!
assert ['a', 'b', 'c', 'd'] == (
IsListOrTuple(positions={2: 'c', 3: 'd'}, length=4) # (6)!
)
assert [1, 2, 3, 4] == IsListOrTuple(3, check_order=False, length=(0, ...)) # (7)!
assert [1, 2, 3] == IsListOrTuple(AnyThing, AnyThing, 3) # (8)!
```
1. Unlike using sets for comparison, we can do order-insensitive comparisons on objects that are not hashable.
2. And we won't get caught out by duplicate values
3. Here we're just checking the first 3 items, the compared list or tuple can be of any length
4. Compared list is not long enough
5. Compare using `positions`, here no length if enforced
6. Compare using `positions` but with a length constraint
7. Here we're just confirming that the value `3` is in the list
8. If you don't care about the first few values of a list or tuple,
you can use [`AnyThing`][dirty_equals.AnyThing] in your arguments.
"""
if positions is not None:
self.positions: Optional[dict[int, Any]] = positions
if items:
raise TypeError(f'{self.__class__.__name__} requires either args or positions, not both')
if not check_order:
raise TypeError('check_order=False is not compatible with positions')
else:
self.positions = None
self.items = items
self.check_order = check_order
self.length: Any = length
if self.length is not None and not isinstance(self.length, int):
if self.length == Ellipsis:
self.length = 0, ...
else:
self.length = tuple(self.length)
super().__init__(
*items,
positions=Omit if positions is None else positions,
length=_length_repr(self.length),
check_order=self.check_order and Omit,
)
def equals(self, other: Any) -> bool:
if not isinstance(other, self.allowed_type):
return False
if not _length_correct(self.length, other):
return False
if self.check_order:
if self.positions is None:
if self.length is None:
return list(self.items) == list(other)
else:
return list(self.items) == list(other[: len(self.items)])
else:
return all(v == other[k] for k, v in self.positions.items())
else:
# order insensitive comparison
# if we haven't checked length yet, check it now
if self.length is None and len(other) != len(self.items):
return False
other_copy = list(other)
for item in self.items:
try:
other_copy.remove(item)
except ValueError:
return False
return True
class IsList(IsListOrTuple[list[Any]]):
"""
All the same functionality as [`IsListOrTuple`][dirty_equals.IsListOrTuple], but the compared value must be a list.
```py title="IsList"
from dirty_equals import IsList
assert [1, 2, 3] == IsList(1, 2, 3)
assert [1, 2, 3] == IsList(positions={2: 3})
assert [1, 2, 3] == IsList(1, 2, 3, check_order=False)
assert [1, 2, 3, 4] == IsList(1, 2, 3, length=4)
assert [1, 2, 3, 4] == IsList(1, 2, 3, length=(4, 5))
assert [1, 2, 3, 4] == IsList(1, 2, 3, length=...)
assert (1, 2, 3) != IsList(1, 2, 3)
```
"""
allowed_type = list
class IsTuple(IsListOrTuple[tuple[Any, ...]]):
"""
All the same functionality as [`IsListOrTuple`][dirty_equals.IsListOrTuple], but the compared value must be a tuple.
```py title="IsTuple"
from dirty_equals import IsTuple
assert (1, 2, 3) == IsTuple(1, 2, 3)
assert (1, 2, 3) == IsTuple(positions={2: 3})
assert (1, 2, 3) == IsTuple(1, 2, 3, check_order=False)
assert (1, 2, 3, 4) == IsTuple(1, 2, 3, length=4)
assert (1, 2, 3, 4) == IsTuple(1, 2, 3, length=(4, 5))
assert (1, 2, 3, 4) == IsTuple(1, 2, 3, length=...)
assert [1, 2, 3] != IsTuple(1, 2, 3)
```
"""
allowed_type = tuple
def _length_repr(length: 'LengthType') -> Any:
if length is None:
return Omit
elif isinstance(length, int) or length is Ellipsis:
return length
else:
if len(length) != 2:
raise TypeError(f'length must be a tuple of length 2, not {len(length)}')
max_value = length[1] if isinstance(length[1], int) else plain_repr('...')
return length[0], max_value
def _length_correct(length: 'LengthType', other: 'Sized') -> bool:
if isinstance(length, int):
if len(other) != length:
return False
elif isinstance(length, tuple):
other_len = len(other)
min_length, max_length = length
if other_len < min_length:
return False
if isinstance(max_length, int) and other_len > max_length:
return False
return True
dirty-equals-0.10.0/dirty_equals/_strings.py 0000664 0000000 0000000 00000011757 15063277303 0021143 0 ustar 00root root 0000000 0000000 import re
from re import Pattern
from typing import Any, Literal, Optional, TypeVar, Union
from ._base import DirtyEquals
from ._utils import Omit, plain_repr
T = TypeVar('T', str, bytes)
__all__ = 'IsStr', 'IsBytes', 'IsAnyStr'
class IsAnyStr(DirtyEquals[T]):
"""
Comparison of `str` or `bytes` objects.
This class allow comparison with both `str` and `bytes` but is subclassed
by [`IsStr`][dirty_equals.IsStr] and [`IsBytes`][dirty_equals.IsBytes] which restrict comparison to
`str` or `bytes` respectively.
"""
expected_types: tuple[type[Any], ...] = (str, bytes)
def __init__(
self,
*,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
case: Literal['upper', 'lower', None] = None,
regex: Union[None, T, Pattern[T]] = None,
regex_flags: int = 0,
):
"""
Args:
min_length: minimum length of the string/bytes
max_length: maximum length of the string/bytes
case: check case of the string/bytes
regex: regular expression to match the string/bytes with, `re.fullmatch` is used.
This can be a compiled regex, or a string or bytes.
regex_flags: optional flags for the regular expression
Examples:
```py title="IsAnyStr"
from dirty_equals import IsAnyStr
assert 'foobar' == IsAnyStr()
assert b'foobar' == IsAnyStr()
assert 123 != IsAnyStr()
assert 'foobar' == IsAnyStr(regex='foo...')
assert 'foobar' == IsAnyStr(regex=b'foo...') # (1)!
assert 'foobar' == IsAnyStr(min_length=6)
assert 'foobar' != IsAnyStr(min_length=8)
assert 'foobar' == IsAnyStr(case='lower')
assert 'Foobar' != IsAnyStr(case='lower')
```
1. `regex` can be either a string or bytes, `IsAnyStr` will take care of conversion so checks work.
"""
self.min_length = min_length
self.max_length = max_length
self.case = case
self._flex = len(self.expected_types) > 1
if regex is None:
self.regex: Union[None, T, Pattern[T]] = None
self.regex_flags: int = 0
else:
self.regex, self.regex_flags = self._prepare_regex(regex, regex_flags)
super().__init__(
min_length=Omit if min_length is None else min_length,
max_length=Omit if max_length is None else max_length,
case=case or Omit,
regex=regex or Omit,
regex_flags=Omit if regex_flags == 0 else plain_repr(repr(re.RegexFlag(regex_flags))),
)
def equals(self, other: Any) -> bool:
if type(other) not in self.expected_types:
return False
if self.regex is not None:
if self._flex and isinstance(other, str):
other = other.encode()
if not re.fullmatch(self.regex, other, flags=self.regex_flags):
return False
len_ = len(other)
if self.min_length is not None and len_ < self.min_length:
return False
if self.max_length is not None and len_ > self.max_length:
return False
if self.case == 'upper' and not other.isupper():
return False
if self.case == 'lower' and not other.islower():
return False
return True
def _prepare_regex(self, regex: Union[T, Pattern[T]], regex_flags: int) -> tuple[Union[T, Pattern[T]], int]:
if isinstance(regex, re.Pattern):
if self._flex:
# less performant, but more flexible
if regex_flags == 0 and regex.flags != re.UNICODE:
regex_flags = regex.flags & ~re.UNICODE
regex = regex.pattern
elif regex_flags != 0:
regex = regex.pattern
if self._flex and isinstance(regex, str):
regex = regex.encode() # type: ignore[assignment]
return regex, regex_flags
class IsStr(IsAnyStr[str]):
"""
Checks if the value is a string, and optionally meets some constraints.
`IsStr` is a subclass of [`IsAnyStr`][dirty_equals.IsAnyStr] and therefore allows all the same arguments.
Examples:
```py title="IsStr"
from dirty_equals import IsStr
assert 'foobar' == IsStr()
assert b'foobar' != IsStr()
assert 'foobar' == IsStr(regex='foo...')
assert 'FOOBAR' == IsStr(min_length=5, max_length=10, case='upper')
```
"""
expected_types = (str,)
class IsBytes(IsAnyStr[bytes]):
"""
Checks if the value is a bytes object, and optionally meets some constraints.
`IsBytes` is a subclass of [`IsAnyStr`][dirty_equals.IsAnyStr] and therefore allows all the same arguments.
Examples:
```py title="IsBytes"
from dirty_equals import IsBytes
assert b'foobar' == IsBytes()
assert 'foobar' != IsBytes()
assert b'foobar' == IsBytes(regex=b'foo...')
assert b'FOOBAR' == IsBytes(min_length=5, max_length=10, case='upper')
```
"""
expected_types = (bytes,)
dirty-equals-0.10.0/dirty_equals/_utils.py 0000664 0000000 0000000 00000002141 15063277303 0020575 0 ustar 00root root 0000000 0000000 __all__ = 'plain_repr', 'PlainRepr', 'Omit', 'get_dict_arg'
from typing import Any
class PlainRepr:
"""
Hack to allow repr of string without quotes.
"""
def __init__(self, v: str):
self.v = v
def __repr__(self) -> str:
return self.v
def plain_repr(v: str) -> PlainRepr:
return PlainRepr(v)
# used to omit arguments from repr
Omit = object()
def get_dict_arg(
name: str, expected_args: tuple[dict[Any, Any], ...], expected_kwargs: dict[str, Any]
) -> dict[Any, Any]:
"""
Used to enforce init logic similar to `dict(...)`.
"""
if expected_kwargs:
value = expected_kwargs
if expected_args:
raise TypeError(f'{name} requires either a single argument or kwargs, not both')
elif not expected_args:
value = {}
elif len(expected_args) == 1:
value = expected_args[0]
if not isinstance(value, dict):
raise TypeError(f'expected_values must be a dict, got {type(value)}')
else:
raise TypeError(f'{name} expected at most 1 argument, got {len(expected_args)}')
return value
dirty-equals-0.10.0/dirty_equals/py.typed 0000664 0000000 0000000 00000000000 15063277303 0020413 0 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/dirty_equals/version.py 0000664 0000000 0000000 00000000023 15063277303 0020760 0 ustar 00root root 0000000 0000000 VERSION = '0.10.0'
dirty-equals-0.10.0/docs/ 0000775 0000000 0000000 00000000000 15063277303 0015151 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/docs/CNAME 0000664 0000000 0000000 00000000033 15063277303 0015713 0 ustar 00root root 0000000 0000000 dirty-equals.helpmanual.io
dirty-equals-0.10.0/docs/img/ 0000775 0000000 0000000 00000000000 15063277303 0015725 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/docs/img/dirty-equals-logo-base.svg 0000664 0000000 0000000 00000007147 15063277303 0022750 0 ustar 00root root 0000000 0000000
dirty-equals-0.10.0/docs/img/dirty-equals-logo-favicon.svg 0000664 0000000 0000000 00000006031 15063277303 0023452 0 ustar 00root root 0000000 0000000
dirty-equals-0.10.0/docs/img/favicon.png 0000664 0000000 0000000 00000001373 15063277303 0020064 0 ustar 00root root 0000000 0000000 ‰PNG
IHDR szzô pHYs à ÃÇo¨d tEXtSoftware www.inkscape.org›î< ˆIDATX…å–»kQÆgæÎî&»³â*D"AˆITDƒ ¨Xù +Aƒ•vZØZiá¿F+"–"¤2ŠbmcáѨEDA³›5Ù™c‘çξftŒˆ_·w¾9¿ïÌœ½wà—d±ä3™‡…$ëF’›]#¯]#ÏH¸±¶ê…î¼ÍˆëÈ׈ºF4gs&êý“—ÊÛœÄ: è Ð ]kM
ÊžžH4€k8,"wUé‹`/—<Í/fI @Þá ª<ÒQü ¾§[+0ÕÉgE¨%ªr#|I» ï°Ž _¨ÀôopÓìÈÙÖ¨ªÜ‹ H»FÊ®‘ǮͩvƦ33\ä`~Þ„"·Kµàà…/5lKðëD›¨N[VW5Љ†lklÈÐxò†¤:¯WÍötð|\»Xò=ÎI#|•û@‡¦®¨žeµI'p(Rc´e Aö6–’[%?¸€®‘}ÀÎå¥@þpá÷ÜìñcÃWU7t”ÆÐã–W ¡¿sŸ$æ+@ÉCõkÁÛ°me³)öXÜÅÅ×Ó@ôò\ç+r†# ãݾ¬y-~÷˜,l±Œ¼6¶¹a–ø³!tL‡4£žî2¶±.)Ú®2Vòƒó1á°¸¿z[\/`¸h)z¬mé|¤¶Ð0Ó¾´7@¡IÌCA"¯ËªBèé`Û$®‘GÀÑ8Å“’ÀCKDÇÿ|1ŽKd+޼@Ù¶®pe²äënëÌ¢'Ï눟R[‹À æóÕ ¸#b冈ÿýU³¨Œ¾ŽÌùLBó/¢T.Åv[“Ý}¡R®ò¨&Y÷ß×O9¶²f”«þÀ IEND®B`‚ dirty-equals-0.10.0/docs/img/logo-text.svg 0000664 0000000 0000000 00000017101 15063277303 0020370 0 ustar 00root root 0000000 0000000
dirty-equals-0.10.0/docs/img/logo-white.svg 0000664 0000000 0000000 00000002467 15063277303 0020535 0 ustar 00root root 0000000 0000000
dirty-equals-0.10.0/docs/index.md 0000664 0000000 0000000 00000010227 15063277303 0016604 0 ustar 00root root 0000000 0000000
Doing dirty (but extremely useful) things with equals.
---
{{ version }}
**dirty-equals** is a python library that (mis)uses the `__eq__` method to make python code (generally unit tests)
more declarative and therefore easier to read and write.
*dirty-equals* can be used in whatever context you like, but it comes into its own when writing unit tests for
applications where you're commonly checking the response to API calls and the contents of a database.
## Usage
Here's a trivial example of what *dirty-equals* can do:
```{.py title="Trivial Usage" test="skip"}
from dirty_equals import IsPositive
assert 1 == IsPositive # (1)!
assert -2 == IsPositive # this will fail! (2)
```
1. This `assert` will pass since `1` is indeed positive, so the result of `1 == IsPositive` is `True`.
2. This will fail (raise a `AssertionError`) since `-2` is not positive,
so the result of `-2 == IsPositive` is `False`.
**Not that interesting yet!**, but consider the following unit test code using **dirty-equals**:
```py title="More Powerful Usage" lint="skip"
from dirty_equals import IsJson, IsNow, IsPositiveInt, IsStr
def test_user_endpoint(client: 'HttpClient', db_conn: 'Database'):
client.post('/users/create/', data=...)
user_data = db_conn.fetchrow('select * from users')
assert user_data == {
'id': IsPositiveInt, # (1)!
'username': 'samuelcolvin', # (2)!
'avatar_file': IsStr(regex=r'/[a-z0-9\-]{10}/example\.png'), # (3)!
'settings_json': IsJson({'theme': 'dark', 'language': 'en'}), # (4)!
'created_ts': IsNow(delta=3), # (5)!
}
```
1. We don't actually care what the `id` is, just that it's present, it's an `int` and it's positive.
2. We can use a normal key and value here since we know exactly what value `username` should have before we test it.
3. `avatar_file` is a string, but we don't know all of the string before the `assert`,
just the format (regex) it should match.
4. `settings_json` is a `JSON` string, but it's simpler and more robust to confirm it represents a particular python
object rather than compare strings.
5. `created_at` is a `datetime`, although we don't know (or care) about its exact value;
since the user was just created we know it must be close to now. `delta` is optional, it defaults to 2 seconds.
Without **dirty-equals**, you'd have to compare individual fields and/or modify some fields before comparison
- the test would not be declarative or as clear.
**dirty-equals** can do so much more than that, for example:
* [`IsPartialDict`][dirty_equals.IsPartialDict] lets you compare a subset of a dictionary
* [`IsStrictDict`][dirty_equals.IsStrictDict] lets you confirm order in a dictionary
* [`IsList`][dirty_equals.IsList] and [`IsTuple`][dirty_equals.IsTuple] lets you compare partial lists and tuples,
with or without order constraints
* nesting any of these types inside any others
* [`IsInstance`][dirty_equals.IsInstance] lets you simply confirm the type of an object
* You can even use [boolean operators](./usage.md#boolean-logic) `|` and `&` to combine multiple conditions
* and much more...
## Installation
Simply:
```bash
pip install dirty-equals
```
**dirty-equals** requires **Python 3.9+**.
dirty-equals-0.10.0/docs/internals.md 0000664 0000000 0000000 00000002171 15063277303 0017473 0 ustar 00root root 0000000 0000000 # Internals
## How the magic of `DirtyEquals.__eq__` works?
When you call `x == y`, Python first calls `x.__eq__(y)`. This would not help us
much, because we would have to keep an eye on order of the arguments when
comparing to `DirtyEquals` objects. But that's where were another feature of
Python comes in.
When `x.__eq__(y)` returns the `NotImplemented` object, then Python will try to
call `y.__eq__(x)`. Objects in the standard library return that value when they
don't know how to compare themselves to objects of `type(y)` (Without checking
the C source I can't be certain if this assumption holds for all classes, but it
works for all the basic ones).
In [`pathlib.PurePath`](https://github.com/python/cpython/blob/aebbd7579a421208f48dd6884b67dbd3278b71ad/Lib/pathlib.py#L751)
you can see an example how that is implemented in Python.
> By default, object implements `__eq__()` by using `is`,
> returning `NotImplemented` in the case of a false comparison:
> `True if x is y else NotImplemented`.
See the Python documentation for more information ([`object.__eq__`](https://docs.python.org/3/reference/datamodel.html#object.__eq__)).
dirty-equals-0.10.0/docs/plugins.py 0000664 0000000 0000000 00000004736 15063277303 0017216 0 ustar 00root root 0000000 0000000 import logging
import os
import re
from mkdocs.config import Config
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
try:
import pytest
except ImportError:
pytest = None
logger = logging.getLogger('mkdocs.test_examples')
def on_pre_build(config: Config):
pass
def on_files(files: Files, config: Config) -> Files:
return remove_files(files)
def remove_files(files: Files) -> Files:
to_remove = []
for file in files:
if file.src_path in {'plugins.py'}:
to_remove.append(file)
elif file.src_path.startswith('__pycache__/'):
to_remove.append(file)
logger.debug('removing files: %s', [f.src_path for f in to_remove])
for f in to_remove:
files.remove(f)
return files
def on_page_markdown(markdown: str, page: Page, config: Config, files: Files) -> str:
markdown = remove_code_fence_attributes(markdown)
return add_version(markdown, page)
def add_version(markdown: str, page: Page) -> str:
if page.file.src_uri == 'index.md':
version_ref = os.getenv('GITHUB_REF')
if version_ref and version_ref.startswith('refs/tags/'):
version = re.sub('^refs/tags/', '', version_ref.lower())
url = f'https://github.com/samuelcolvin/dirty-equals/releases/tag/{version}'
version_str = f'Documentation for version: [{version}]({url})'
elif sha := os.getenv('GITHUB_SHA'):
sha = sha[:7]
url = f'https://github.com/samuelcolvin/dirty-equals/commit/{sha}'
version_str = f'Documentation for development version: [{sha}]({url})'
else:
version_str = 'Documentation for development version'
markdown = re.sub(r'{{ *version *}}', version_str, markdown)
return markdown
def remove_code_fence_attributes(markdown: str) -> str:
"""
There's no way to add attributes to code fences that works with both pycharm and mkdocs, hence we use
`py key="value"` to provide attributes to pytest-examples, then remove those attributes here.
https://youtrack.jetbrains.com/issue/IDEA-297873 & https://python-markdown.github.io/extensions/fenced_code_blocks/
"""
def remove_attrs(match: re.Match[str]) -> str:
suffix = re.sub(
r' (?:test|lint|upgrade|group|requires|output|rewrite_assert)=".+?"', '', match.group(2), flags=re.M
)
return f'{match.group(1)}{suffix}'
return re.sub(r'^( *``` *py)(.*)', remove_attrs, markdown, flags=re.M)
dirty-equals-0.10.0/docs/types/ 0000775 0000000 0000000 00000000000 15063277303 0016315 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/docs/types/boolean.md 0000664 0000000 0000000 00000000113 15063277303 0020251 0 ustar 00root root 0000000 0000000 # Boolean Types
::: dirty_equals.IsTrueLike
::: dirty_equals.IsFalseLike
dirty-equals-0.10.0/docs/types/custom.md 0000664 0000000 0000000 00000002413 15063277303 0020151 0 ustar 00root root 0000000 0000000 # Custom Types
::: dirty_equals._base.DirtyEquals
options:
merge_init_into_class: false
## Custom Type Example
To demonstrate the use of custom types, we'll create a custom type that matches any even number.
We won't inherit from [`IsNumeric`][dirty_equals.IsNumeric] in this case to keep the example simple.
```py title="IsEven"
from decimal import Decimal
from typing import Any, Union
from dirty_equals import DirtyEquals, IsOneOf
class IsEven(DirtyEquals[Union[int, float, Decimal]]):
def equals(self, other: Any) -> bool:
return other % 2 == 0
assert 2 == IsEven
assert 3 != IsEven
assert 'foobar' != IsEven
assert 3 == IsEven | IsOneOf(3)
```
There are a few advantages of inheriting from [`DirtyEquals`][dirty_equals.DirtyEquals] compared to just
implementing your own class with an `__eq__` method:
1. `TypeError` and `ValueError` in `equals` are caught and result in a not-equals result.
2. A useful `__repr__` is generated, and modified if the `==` operation returns `True`,
see [pytest compatibility](../usage.md#__repr__-and-pytest-compatibility)
3. [boolean logic](../usage.md#boolean-logic) works out of the box
4. [Uninitialised usage](../usage.md#initialised-vs-class-comparison)
(`IsEven` rather than `IsEven()`) works out of the box
dirty-equals-0.10.0/docs/types/datetime.md 0000664 0000000 0000000 00000003535 15063277303 0020441 0 ustar 00root root 0000000 0000000 # Date and Time Types
::: dirty_equals.IsDatetime
### Timezones
Timezones are hard, anyone who claims otherwise is either a genius, a liar, or an idiot.
`IsDatetime` and its subtypes (e.g. [`IsNow`][dirty_equals.IsNow]) can be used in two modes,
based on the `enforce_tz` parameter:
* `enforce_tz=True` (the default):
* if the datetime wrapped by `IsDatetime` is timezone naive, the compared value must also be timezone naive.
* if the datetime wrapped by `IsDatetime` has a timezone, the compared value must have a
timezone with the same offset.
* `enforce_tz=False`:
* if the datetime wrapped by `IsDatetime` is timezone naive, the compared value can either be naive or have a
timezone all that matters is the datetime values match.
* if the datetime wrapped by `IsDatetime` has a timezone, the compared value needs to represent the same point in
time - either way it must have a timezone.
Example
```py title="IsDatetime & timezones" requires="3.9"
from datetime import datetime
from zoneinfo import ZoneInfo
from dirty_equals import IsDatetime
tz_london = ZoneInfo('Europe/London')
new_year_london = datetime(2000, 1, 1, tzinfo=tz_london)
tz_nyc = ZoneInfo('America/New_York')
new_year_eve_nyc = datetime(1999, 12, 31, 19, 0, 0, tzinfo=tz_nyc)
assert new_year_eve_nyc == IsDatetime(approx=new_year_london, enforce_tz=False)
assert new_year_eve_nyc != IsDatetime(approx=new_year_london, enforce_tz=True)
new_year_naive = datetime(2000, 1, 1)
assert new_year_naive != IsDatetime(approx=new_year_london, enforce_tz=False)
assert new_year_naive != IsDatetime(approx=new_year_eve_nyc, enforce_tz=False)
assert new_year_london == IsDatetime(approx=new_year_naive, enforce_tz=False)
assert new_year_eve_nyc != IsDatetime(approx=new_year_naive, enforce_tz=False)
```
::: dirty_equals.IsNow
::: dirty_equals.IsDate
::: dirty_equals.IsToday
dirty-equals-0.10.0/docs/types/dict.md 0000664 0000000 0000000 00000000212 15063277303 0017555 0 ustar 00root root 0000000 0000000 # Dictionary Types
::: dirty_equals.IsDict
::: dirty_equals.IsPartialDict
::: dirty_equals.IsIgnoreDict
::: dirty_equals.IsStrictDict
dirty-equals-0.10.0/docs/types/inspection.md 0000664 0000000 0000000 00000000203 15063277303 0021005 0 ustar 00root root 0000000 0000000 # Type Inspection
::: dirty_equals.IsInstance
::: dirty_equals.HasName
::: dirty_equals.HasRepr
::: dirty_equals.HasAttributes
dirty-equals-0.10.0/docs/types/numeric.md 0000664 0000000 0000000 00000002434 15063277303 0020304 0 ustar 00root root 0000000 0000000 # Numeric Types
::: dirty_equals.IsInt
options:
merge_init_into_class: false
separate_signature: false
::: dirty_equals.IsFloat
options:
merge_init_into_class: false
separate_signature: false
::: dirty_equals.IsPositive
options:
merge_init_into_class: false
::: dirty_equals.IsNegative
options:
merge_init_into_class: false
::: dirty_equals.IsNonNegative
options:
merge_init_into_class: false
::: dirty_equals.IsNonPositive
options:
merge_init_into_class: false
::: dirty_equals.IsPositiveInt
options:
merge_init_into_class: false
::: dirty_equals.IsNegativeInt
options:
merge_init_into_class: false
::: dirty_equals.IsPositiveFloat
options:
merge_init_into_class: false
::: dirty_equals.IsNegativeFloat
options:
merge_init_into_class: false
::: dirty_equals.IsFloatInf
options:
merge_init_into_class: false
::: dirty_equals.IsFloatInfPos
options:
merge_init_into_class: false
::: dirty_equals.IsFloatInfNeg
options:
merge_init_into_class: false
::: dirty_equals.IsFloatNan
options:
merge_init_into_class: false
::: dirty_equals.IsApprox
::: dirty_equals.IsNumber
options:
merge_init_into_class: false
::: dirty_equals.IsNumeric
dirty-equals-0.10.0/docs/types/other.md 0000664 0000000 0000000 00000000634 15063277303 0017763 0 ustar 00root root 0000000 0000000 # Other Types
::: dirty_equals.FunctionCheck
::: dirty_equals.IsInstance
::: dirty_equals.IsJson
::: dirty_equals.IsUUID
::: dirty_equals.AnyThing
::: dirty_equals.IsOneOf
::: dirty_equals.IsUrl
::: dirty_equals.IsHash
::: dirty_equals.IsIP
::: dirty_equals.IsDataclassType
::: dirty_equals.IsDataclass
::: dirty_equals.IsPartialDataclass
::: dirty_equals.IsStrictDataclass
::: dirty_equals.IsEnum
dirty-equals-0.10.0/docs/types/sequence.md 0000664 0000000 0000000 00000000230 15063277303 0020442 0 ustar 00root root 0000000 0000000 # Sequence Types
::: dirty_equals.IsListOrTuple
::: dirty_equals.IsList
::: dirty_equals.IsTuple
::: dirty_equals.HasLen
::: dirty_equals.Contains
dirty-equals-0.10.0/docs/types/string.md 0000664 0000000 0000000 00000000134 15063277303 0020143 0 ustar 00root root 0000000 0000000 # String Types
::: dirty_equals.IsAnyStr
::: dirty_equals.IsStr
::: dirty_equals.IsBytes
dirty-equals-0.10.0/docs/usage.md 0000664 0000000 0000000 00000007354 15063277303 0016610 0 ustar 00root root 0000000 0000000 ## Boolean Logic
*dirty-equals* types can be combined based on either `&`
(and, all checks must be `True` for the combined check to be `True`) or `|`
(or, any check can be `True` for the combined check to be `True`).
Types can also be inverted using the `~` operator, this is equivalent to using `!=` instead of `==`.
Example:
```py title="Boolean Combination of Types"
from dirty_equals import Contains, HasLen
assert ['a', 'b', 'c'] == HasLen(3) & Contains('a') # (1)!
assert ['a', 'b', 'c'] == HasLen(3) | Contains('z') # (2)!
assert ['a', 'b', 'c'] != Contains('z')
assert ['a', 'b', 'c'] == ~Contains('z')
```
1. The object on the left has to both have length 3 **and** contain `"a"`
2. The object on the left has to either have length 3 **or** contain `"z"`
## Initialised vs. Class comparison
!!! warning
This does not work with PyPy.
*dirty-equals* allows comparison with types regardless of whether they've been initialised.
This saves users adding `()` in lots of places.
Example:
```py title="Initialised vs. Uninitialised"
from dirty_equals import IsInt
# these two cases are the same
assert 1 == IsInt
assert 1 == IsInt()
```
!!! Note
Types that require at least on argument when being initialised (like [`IsApprox`][dirty_equals.IsApprox])
cannot be used like this, comparisons will just return `False`.
## `__repr__` and pytest compatibility
dirty-equals types have reasonable `__repr__` methods, which describe types and generally are a close match
of how they would be created:
```py title="__repr__"
from dirty_equals import IsApprox, IsInt
assert repr(IsInt) == 'IsInt'
assert repr(IsInt()) == 'IsInt()'
assert repr(IsApprox(42)) == 'IsApprox(approx=42)'
```
However, the repr method of types changes when an equals (`==`) operation on them returns a `True`, in this case
the `__repr__` method will return `repr(other)`.
```py title="repr() after comparison"
from dirty_equals import IsInt
v = IsInt()
assert 42 == v
assert repr(v) == '42'
```
This black magic is designed to make the output of pytest when asserts on large objects fail as simple as
possible to read.
Consider the following unit test:
```py title="pytest error example"
from datetime import datetime
from dirty_equals import IsNow, IsPositiveInt
def test_partial_dict():
api_response_data = {
'id': 1, # (1)!
'first_name': 'John',
'last_name': 'Doe',
'created_at': datetime.now().isoformat(),
'phone': '+44 123456789',
}
assert api_response_data == {
'id': IsPositiveInt(),
'first_name': 'John',
'last_name': 'Doe',
'created_at': IsNow(iso_string=True),
# phone number is missing, so the test will fail
}
```
1. For simplicity we've hardcoded `id` here, but in a test it could be any positive int,
hence why we need `IsPositiveInt()`
Here's an except from the output of `pytest -vv` show the error details:
```txt title="pytest output"
E Common items:
E {'created_at': '2022-02-25T15:41:38.493512',
E 'first_name': 'John',
E 'id': 1,
E 'last_name': 'Doe'}
E Left contains 1 more item:
E {'phone': '+44 123456789'}
E Full diff:
E {
E 'created_at': '2022-02-25T15:41:38.493512',
E 'first_name': 'John',
E 'id': 1,
E 'last_name': 'Doe',
E + 'phone': '+44 123456789',
E }
```
It's easy to see that the `phone` key is missing, `id` and `created_at` are represented by the exact
values they were compared to, so don't show as different in the "Full diff" section.
!!! Warning
This black magic only works when using initialised types, if `IsPositiveInt` was used instead `IsPositiveInt()`
in the above example, the output would not be as clean.
dirty-equals-0.10.0/mkdocs.yml 0000664 0000000 0000000 00000005032 15063277303 0016224 0 ustar 00root root 0000000 0000000 site_name: dirty-equals
site_description: Doing dirty (but extremely useful) things with equals.
site_url: https://dirty-equals.helpmanual.io
theme:
name: material
palette:
- scheme: default
primary: blue grey
accent: indigo
toggle:
icon: material/lightbulb
name: Switch to dark mode
- scheme: slate
primary: blue grey
accent: indigo
toggle:
icon: material/lightbulb-outline
name: Switch to light mode
features:
- search.suggest
- search.highlight
- content.tabs.link
- content.code.annotate
icon:
repo: fontawesome/brands/github-alt
logo: img/logo-white.svg
favicon: img/favicon.png
language: en
repo_name: samuelcolvin/dirty-equals
repo_url: https://github.com/samuelcolvin/dirty-equals
edit_uri: ''
nav:
- Introduction: index.md
- Usage: usage.md
- Types:
- types/numeric.md
- types/datetime.md
- types/dict.md
- types/sequence.md
- types/string.md
- types/inspection.md
- types/boolean.md
- types/other.md
- types/custom.md
- Internals: internals.md
markdown_extensions:
- toc:
permalink: true
- admonition
- pymdownx.details
- pymdownx.superfences
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets
- attr_list
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
extra:
version:
provider: mike
analytics:
provider: google
property: G-FLP20728CW
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/samuelcolvin/dirty-equals
- icon: fontawesome/brands/twitter
link: https://twitter.com/samuel_colvin
watch:
- dirty_equals
plugins:
- mike:
alias_type: symlink
canonical_version: latest
- search
- mkdocstrings:
handlers:
python:
options:
show_root_heading: true
show_root_full_path: false
show_source: false
heading_level: 2
merge_init_into_class: true
show_signature_annotations: true
separate_signature: true
signature_crossrefs: true
import:
- url: https://docs.python.org/3/objects.inv
- url: https://docs.pydantic.dev/latest/objects.inv
- mkdocs-simple-hooks:
hooks:
on_pre_build: 'docs.plugins:on_pre_build'
on_files: 'docs.plugins:on_files'
on_page_markdown: 'docs.plugins:on_page_markdown'
dirty-equals-0.10.0/pyproject.toml 0000664 0000000 0000000 00000005752 15063277303 0017146 0 ustar 00root root 0000000 0000000 [build-system]
requires = ['hatchling']
build-backend = 'hatchling.build'
[tool.hatch.version]
path = 'dirty_equals/version.py'
[project]
name = 'dirty-equals'
description = 'Doing dirty (but extremely useful) things with equals.'
authors = [{name = 'Samuel Colvin', email = 's@muelcolvin.com'}]
license = 'MIT'
readme = 'README.md'
classifiers = [
'Development Status :: 4 - Beta',
'Framework :: Pytest',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: Information Technology',
'Intended Audience :: Science/Research',
'Intended Audience :: System Administrators',
'Operating System :: Unix',
'Operating System :: POSIX :: Linux',
'Environment :: Console',
'Environment :: MacOS X',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Internet',
'Typing :: Typed',
]
requires-python = '>=3.9'
optional-dependencies = {pydantic = ['pydantic>=2.4.2'] }
dynamic = ['version']
[project.urls]
Homepage = 'https://github.com/samuelcolvin/dirty-equals'
Documentation = 'https://dirty-equals.helpmanual.io'
Funding = 'https://github.com/sponsors/samuelcolvin'
Source = 'https://github.com/samuelcolvin/dirty-equals'
Changelog = 'https://github.com/samuelcolvin/dirty-equals/releases'
[dependency-groups]
dev = [
"coverage[toml]>=7.6.1",
"mypy>=1.14.1",
"packaging>=25.0",
"pydantic>=2.10.6",
"pytest>=8.3.5",
"pytest-examples>=0.0.18",
"pytest-pretty>=1.2.0",
"ruff>=0.13.1",
"types-pytz>=2024.2.0.20241221",
]
# TODO update these deps to newer versions, I just froze them while migrationg from requirements files
docs = [
"mike==2.1.3",
"mkdocs==1.6.0",
"mkdocs-autorefs==1.0.1",
"mkdocs-material==9.5.31",
"mkdocs-simple-hooks==0.1.5",
"mkdocstrings[python]==0.25.2",
]
[tool.ruff]
line-length = 120
target-version = 'py39'
include = ["dirty_equals/**/*.py", "tests/**/*.py"]
[tool.ruff.lint]
extend-select = ['Q', 'RUF100', 'C90', 'UP', 'I']
ignore = ['E721']
flake8-quotes = {inline-quotes = 'single', multiline-quotes = 'double'}
mccabe = { max-complexity = 14 }
pydocstyle = { convention = 'google' }
[tool.ruff.format]
# don't format python in docstrings, pytest-examples takes care of it
docstring-code-format = false
quote-style = "single"
[tool.pytest.ini_options]
testpaths = "tests"
filterwarnings = "error"
[tool.coverage.run]
source = ["dirty_equals"]
branch = true
[tool.coverage.report]
precision = 2
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"raise NotImplemented",
"if TYPE_CHECKING:",
"@overload",
]
[tool.mypy]
strict = true
warn_return_any = false
show_error_codes = true
dirty-equals-0.10.0/tests/ 0000775 0000000 0000000 00000000000 15063277303 0015363 5 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/tests/__init__.py 0000664 0000000 0000000 00000000000 15063277303 0017462 0 ustar 00root root 0000000 0000000 dirty-equals-0.10.0/tests/mypy_checks.py 0000664 0000000 0000000 00000000664 15063277303 0020261 0 ustar 00root root 0000000 0000000 """
This module is run with mypy to check types can be used correctly externally.
"""
import sys
sys.path.append('.')
from dirty_equals import HasName, HasRepr, IsStr
assert 123 == HasName('int')
assert 123 == HasRepr('123')
assert 123 == HasName(IsStr(regex='i..'))
assert 123 == HasRepr(IsStr(regex=r'\d{3}'))
# type ignore is required (if it wasn't, there would be an error)
assert 123 != HasName(123) # type: ignore[arg-type]
dirty-equals-0.10.0/tests/test_base.py 0000664 0000000 0000000 00000012007 15063277303 0017706 0 ustar 00root root 0000000 0000000 import platform
import pprint
from functools import singledispatch
import packaging.version
import pytest
from dirty_equals import Contains, DirtyEquals, IsApprox, IsInt, IsList, IsNegative, IsOneOf, IsPositive, IsStr
from dirty_equals.version import VERSION
def test_or():
assert 'foo' == IsStr | IsInt
assert 1 == IsStr | IsInt
assert -1 == IsStr | IsNegative | IsPositive
v = IsStr | IsInt
with pytest.raises(AssertionError):
assert 1.5 == v
assert str(v) == 'IsStr | IsInt'
def test_and():
assert 4 == IsPositive & IsInt(lt=5)
v = IsStr & IsInt
with pytest.raises(AssertionError):
assert 1 == v
assert str(v) == 'IsStr & IsInt'
def test_not():
assert 'foo' != IsInt
assert 'foo' == ~IsInt
def test_value_eq():
v = IsStr()
with pytest.raises(AttributeError, match='value is not available until __eq__ has been called'):
v.value
assert 'foo' == v
assert repr(v) == str(v) == "'foo'" == pprint.pformat(v)
assert v.value == 'foo'
def test_value_ne():
v = IsStr()
with pytest.raises(AssertionError):
assert 1 == v
assert repr(v) == str(v) == 'IsStr()' == pprint.pformat(v)
with pytest.raises(AttributeError, match='value is not available until __eq__ has been called'):
v.value
def test_dict_compare():
v = {'foo': 1, 'bar': 2, 'spam': 3}
assert v == {'foo': IsInt, 'bar': IsPositive, 'spam': ~IsStr}
assert v == {'foo': IsInt() & IsApprox(1), 'bar': IsPositive() | IsNegative(), 'spam': ~IsStr()}
@pytest.mark.skipif(platform.python_implementation() == 'PyPy', reason='PyPy does not metaclass dunder methods')
def test_not_repr():
v = ~IsInt
assert str(v) == '~IsInt'
with pytest.raises(AssertionError):
assert 1 == v
assert str(v) == '~IsInt'
def test_not_repr_instance():
v = ~IsInt()
assert str(v) == '~IsInt()'
with pytest.raises(AssertionError):
assert 1 == v
assert str(v) == '~IsInt()'
def test_repr():
v = ~IsInt
assert str(v) == '~IsInt'
assert '1' == v
assert str(v) == "'1'"
@pytest.mark.parametrize(
'v,v_repr',
[
(IsInt, 'IsInt'),
(~IsInt, '~IsInt'),
(IsInt & IsPositive, 'IsInt & IsPositive'),
(IsInt | IsPositive, 'IsInt | IsPositive'),
(IsInt(), 'IsInt()'),
(~IsInt(), '~IsInt()'),
(IsInt() & IsPositive(), 'IsInt() & IsPositive()'),
(IsInt() | IsPositive(), 'IsInt() | IsPositive()'),
(IsInt() & IsPositive, 'IsInt() & IsPositive'),
(IsInt() | IsPositive, 'IsInt() | IsPositive'),
(IsPositive & IsInt(lt=5), 'IsPositive & IsInt(lt=5)'),
(IsOneOf(1, 2, 3), 'IsOneOf(1, 2, 3)'),
],
)
def test_repr_class(v, v_repr):
assert repr(v) == str(v) == v_repr == pprint.pformat(v)
def test_is_approx_without_init():
assert 1 != IsApprox
def test_ne_repr():
v = IsInt
assert repr(v) == str(v) == 'IsInt' == pprint.pformat(v)
assert 'x' != v
assert repr(v) == str(v) == 'IsInt' == pprint.pformat(v)
def test_pprint():
v = [IsList(length=...), 1, [IsList(length=...), 2], 3, IsInt()]
lorem = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'] * 2
with pytest.raises(AssertionError):
assert [lorem, 1, [lorem, 2], 3, '4'] == v
assert repr(v) == (f'[{lorem}, 1, [{lorem}, 2], 3, IsInt()]')
assert pprint.pformat(v) == (
"[['lorem',\n"
" 'ipsum',\n"
" 'dolor',\n"
" 'sit',\n"
" 'amet',\n"
" 'lorem',\n"
" 'ipsum',\n"
" 'dolor',\n"
" 'sit',\n"
" 'amet'],\n"
' 1,\n'
" [['lorem',\n"
" 'ipsum',\n"
" 'dolor',\n"
" 'sit',\n"
" 'amet',\n"
" 'lorem',\n"
" 'ipsum',\n"
" 'dolor',\n"
" 'sit',\n"
" 'amet'],\n"
' 2],\n'
' 3,\n'
' IsInt()]'
)
def test_pprint_not_equal():
v = IsList(*range(30)) # need a big value to trigger pprint
with pytest.raises(AssertionError):
assert [] == v
assert (
pprint.pformat(v)
== (
'IsList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, '
'15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29)'
)
== repr(v)
== str(v)
)
@pytest.mark.parametrize(
'value,dirty',
[
(1, IsOneOf(1, 2, 3)),
(4, ~IsOneOf(1, 2, 3)),
([1, 2, 3], Contains(1) | IsOneOf([])),
([], Contains(1) | IsOneOf([])),
([2], ~(Contains(1) | IsOneOf([]))),
],
)
def test_is_one_of(value, dirty):
assert value == dirty
def test_version():
packaging.version.parse(VERSION)
def test_singledispatch():
@singledispatch
def dispatch(value):
return 'generic'
assert dispatch(IsStr()) == 'generic'
@dispatch.register
def _(value: DirtyEquals):
return 'DirtyEquals'
assert dispatch(IsStr()) == 'DirtyEquals'
@dispatch.register
def _(value: IsStr):
return 'IsStr'
assert dispatch(IsStr()) == 'IsStr'
dirty-equals-0.10.0/tests/test_boolean.py 0000664 0000000 0000000 00000004541 15063277303 0020417 0 ustar 00root root 0000000 0000000 import platform
import pytest
from dirty_equals import IsFalseLike, IsTrueLike
@pytest.mark.parametrize(
'other, expected',
[
(False, IsFalseLike),
(True, ~IsFalseLike),
([], IsFalseLike),
([1], ~IsFalseLike),
((), IsFalseLike),
('', IsFalseLike),
('', IsFalseLike(allow_strings=True)),
((1, 2), ~IsFalseLike),
({}, IsFalseLike),
({1: 'a'}, ~IsFalseLike),
(set(), IsFalseLike),
({'a', 'b', 'c'}, ~IsFalseLike),
(None, IsFalseLike),
(0, IsFalseLike),
(1, ~IsFalseLike),
(0.0, IsFalseLike),
(1.0, ~IsFalseLike),
('0', IsFalseLike(allow_strings=True)),
('1', ~IsFalseLike(allow_strings=True)),
('0.0', IsFalseLike(allow_strings=True)),
('0.000', IsFalseLike(allow_strings=True)),
('1.0', ~IsFalseLike(allow_strings=True)),
('False', IsFalseLike(allow_strings=True)),
('True', ~IsFalseLike(allow_strings=True)),
(0, IsFalseLike(allow_strings=True)),
],
)
def test_is_false_like(other, expected):
assert other == expected
def test_is_false_like_repr():
assert repr(IsFalseLike) == 'IsFalseLike'
assert repr(IsFalseLike()) == 'IsFalseLike()'
assert repr(IsFalseLike(allow_strings=True)) == 'IsFalseLike(allow_strings=True)'
@pytest.mark.skipif(platform.python_implementation() == 'PyPy', reason='PyPy does not metaclass dunder methods')
def test_dirty_not_equals():
with pytest.raises(AssertionError):
assert 0 != IsFalseLike
def test_dirty_not_equals_instance():
with pytest.raises(AssertionError):
assert 0 != IsFalseLike()
def test_invalid_initialization():
with pytest.raises(TypeError, match='takes 1 positional argument but 2 were given'):
IsFalseLike(True)
@pytest.mark.parametrize(
'other, expected',
[
(False, ~IsTrueLike),
(True, IsTrueLike),
([], ~IsTrueLike),
([1], IsTrueLike),
((), ~IsTrueLike),
((1, 2), IsTrueLike),
({}, ~IsTrueLike),
({1: 'a'}, IsTrueLike),
(set(), ~IsTrueLike),
({'a', 'b', 'c'}, IsTrueLike),
(None, ~IsTrueLike),
(0, ~IsTrueLike),
(1, IsTrueLike),
(0.0, ~IsTrueLike),
(1.0, IsTrueLike),
],
)
def test_is_true_like(other, expected):
assert other == expected
dirty-equals-0.10.0/tests/test_datetime.py 0000664 0000000 0000000 00000021507 15063277303 0020575 0 ustar 00root root 0000000 0000000 from datetime import date, datetime, timedelta, timezone
from unittest.mock import Mock
from zoneinfo import ZoneInfo
import pytest
from dirty_equals import IsDate, IsDatetime, IsNow, IsToday
@pytest.mark.parametrize(
'value,dirty,expect_match',
[
pytest.param(datetime(2000, 1, 1), IsDatetime(approx=datetime(2000, 1, 1)), True, id='same'),
# Note: this requires the system timezone to be UTC
pytest.param(946684800, IsDatetime(approx=datetime(2000, 1, 1), unix_number=True), True, id='unix-int'),
# Note: this requires the system timezone to be UTC
pytest.param(946684800.123, IsDatetime(approx=datetime(2000, 1, 1), unix_number=True), True, id='unix-float'),
pytest.param(946684800, IsDatetime(approx=datetime(2000, 1, 1)), False, id='unix-different'),
pytest.param(
'2000-01-01T00:00', IsDatetime(approx=datetime(2000, 1, 1), iso_string=True), True, id='iso-string-true'
),
pytest.param('2000-01-01T00:00', IsDatetime(approx=datetime(2000, 1, 1)), False, id='iso-string-different'),
pytest.param('broken', IsDatetime(approx=datetime(2000, 1, 1)), False, id='iso-string-wrong'),
pytest.param(
'28/01/87', IsDatetime(approx=datetime(1987, 1, 28), format_string='%d/%m/%y'), True, id='string-format'
),
pytest.param('28/01/87', IsDatetime(approx=datetime(2000, 1, 1)), False, id='string-format-different'),
pytest.param('foobar', IsDatetime(approx=datetime(2000, 1, 1)), False, id='string-format-wrong'),
pytest.param(datetime(2000, 1, 1).isoformat(), IsNow(iso_string=True), False, id='isnow-str-different'),
pytest.param([1, 2, 3], IsDatetime(approx=datetime(2000, 1, 1)), False, id='wrong-type'),
pytest.param(
datetime(2020, 1, 1, 12, 13, 14), IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14)), True, id='tz-same'
),
pytest.param(
datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc),
IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14), enforce_tz=False),
True,
id='tz-utc',
),
pytest.param(
datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc),
IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14)),
False,
id='tz-utc-different',
),
pytest.param(
datetime(2020, 1, 1, 12, 13, 14),
IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc), enforce_tz=False),
False,
id='tz-approx-tz',
),
pytest.param(
datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone(offset=timedelta(hours=1))),
IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14), enforce_tz=False),
True,
id='tz-1-hour',
),
pytest.param(
datetime(2022, 2, 15, 15, 15, tzinfo=ZoneInfo('Europe/London')),
IsDatetime(approx=datetime(2022, 2, 15, 10, 15, tzinfo=ZoneInfo('America/New_York')), enforce_tz=False),
True,
id='tz-both-tz',
),
pytest.param(
datetime(2022, 2, 15, 15, 15, tzinfo=ZoneInfo('Europe/London')),
IsDatetime(approx=datetime(2022, 2, 15, 10, 15, tzinfo=ZoneInfo('America/New_York'))),
False,
id='tz-both-tz-different',
),
pytest.param(datetime(2000, 1, 1), IsDatetime(ge=datetime(2000, 1, 1)), True, id='ge'),
pytest.param(datetime(1999, 1, 1), IsDatetime(ge=datetime(2000, 1, 1)), False, id='ge-not'),
pytest.param(datetime(2000, 1, 2), IsDatetime(gt=datetime(2000, 1, 1)), True, id='gt'),
pytest.param(datetime(2000, 1, 1), IsDatetime(gt=datetime(2000, 1, 1)), False, id='gt-not'),
],
)
def test_is_datetime(value, dirty, expect_match):
if expect_match:
assert value == dirty
else:
assert value != dirty
@pytest.mark.skipif(ZoneInfo is None, reason='requires zoneinfo')
def test_is_datetime_zoneinfo():
london = datetime(2022, 2, 15, 15, 15, tzinfo=ZoneInfo('Europe/London'))
ny = datetime(2022, 2, 15, 10, 15, tzinfo=ZoneInfo('America/New_York'))
assert london != IsDatetime(approx=ny)
assert london == IsDatetime(approx=ny, enforce_tz=False)
def test_is_now_dt():
is_now = IsNow()
dt = datetime.now()
assert dt == is_now
assert str(is_now) == repr(dt)
def test_is_now_str():
assert datetime.now().isoformat() == IsNow(iso_string=True)
def test_repr():
v = IsDatetime(approx=datetime(2032, 1, 2, 3, 4, 5), iso_string=True)
assert str(v) == 'IsDatetime(approx=datetime.datetime(2032, 1, 2, 3, 4, 5), iso_string=True)'
@pytest.mark.skipif(ZoneInfo is None, reason='requires zoneinfo')
def test_is_now_tz():
utc_now = datetime.now(timezone.utc)
now_ny = utc_now.astimezone(ZoneInfo('America/New_York'))
assert now_ny == IsNow(tz='America/New_York')
# depends on the time of year and DST
assert now_ny == IsNow(tz=timezone(timedelta(hours=-5))) | IsNow(tz=timezone(timedelta(hours=-4)))
now = datetime.now()
assert now == IsNow
assert now.timestamp() == IsNow(unix_number=True)
assert now.timestamp() != IsNow
assert now.isoformat() == IsNow(iso_string=True)
assert now.isoformat() != IsNow
assert utc_now == IsNow(tz=timezone.utc)
def test_delta():
assert IsNow(delta=timedelta(hours=2)).delta == timedelta(seconds=7200)
assert IsNow(delta=3600).delta == timedelta(seconds=3600)
assert IsNow(delta=3600.1).delta == timedelta(seconds=3600, microseconds=100000)
def test_is_now_relative(monkeypatch):
mock = Mock(return_value=datetime(2020, 1, 1, 12, 13, 14))
monkeypatch.setattr(IsNow, '_get_now', mock)
assert IsNow() == datetime(2020, 1, 1, 12, 13, 14)
@pytest.mark.skipif(ZoneInfo is None, reason='requires zoneinfo')
def test_tz():
new_year_london = datetime(2000, 1, 1, tzinfo=ZoneInfo('Europe/London'))
new_year_eve_nyc = datetime(1999, 12, 31, 19, 0, 0, tzinfo=ZoneInfo('America/New_York'))
assert new_year_eve_nyc == IsDatetime(approx=new_year_london, enforce_tz=False)
assert new_year_eve_nyc != IsDatetime(approx=new_year_london, enforce_tz=True)
new_year_naive = datetime(2000, 1, 1)
assert new_year_naive != IsDatetime(approx=new_year_london, enforce_tz=False)
assert new_year_naive != IsDatetime(approx=new_year_eve_nyc, enforce_tz=False)
assert new_year_london == IsDatetime(approx=new_year_naive, enforce_tz=False)
assert new_year_eve_nyc != IsDatetime(approx=new_year_naive, enforce_tz=False)
@pytest.mark.parametrize(
'value,dirty,expect_match',
[
pytest.param(date(2000, 1, 1), IsDate(approx=date(2000, 1, 1)), True, id='same'),
pytest.param('2000-01-01', IsDate(approx=date(2000, 1, 1), iso_string=True), True, id='iso-string-true'),
pytest.param('2000-01-01', IsDate(approx=date(2000, 1, 1)), False, id='iso-string-different'),
pytest.param('2000-01-01T00:00', IsDate(approx=date(2000, 1, 1)), False, id='iso-string-different'),
pytest.param('broken', IsDate(approx=date(2000, 1, 1)), False, id='iso-string-wrong'),
pytest.param('28/01/87', IsDate(approx=date(1987, 1, 28), format_string='%d/%m/%y'), True, id='string-format'),
pytest.param('28/01/87', IsDate(approx=date(2000, 1, 1)), False, id='string-format-different'),
pytest.param('foobar', IsDate(approx=date(2000, 1, 1)), False, id='string-format-wrong'),
pytest.param([1, 2, 3], IsDate(approx=date(2000, 1, 1)), False, id='wrong-type'),
pytest.param(
datetime(2000, 1, 1, 10, 11, 12), IsDate(approx=date(2000, 1, 1)), False, id='wrong-type-datetime'
),
pytest.param(date(2020, 1, 1), IsDate(approx=date(2020, 1, 1)), True, id='tz-same'),
pytest.param(date(2000, 1, 1), IsDate(ge=date(2000, 1, 1)), True, id='ge'),
pytest.param(date(1999, 1, 1), IsDate(ge=date(2000, 1, 1)), False, id='ge-not'),
pytest.param(date(2000, 1, 2), IsDate(gt=date(2000, 1, 1)), True, id='gt'),
pytest.param(date(2000, 1, 1), IsDate(gt=date(2000, 1, 1)), False, id='gt-not'),
pytest.param(date(2000, 1, 1), IsDate(gt=date(2000, 1, 1), delta=10), False, id='delta-int'),
pytest.param(date(2000, 1, 1), IsDate(gt=date(2000, 1, 1), delta=10.5), False, id='delta-float'),
pytest.param(
date(2000, 1, 1), IsDate(gt=date(2000, 1, 1), delta=timedelta(seconds=10)), False, id='delta-timedelta'
),
],
)
def test_is_date(value, dirty, expect_match):
if expect_match:
assert value == dirty
else:
assert value != dirty
def test_is_today():
today = date.today()
assert today == IsToday
assert today + timedelta(days=2) != IsToday
assert today.isoformat() == IsToday(iso_string=True)
assert today.isoformat() != IsToday()
assert today.strftime('%Y/%m/%d') == IsToday(format_string='%Y/%m/%d')
assert today.strftime('%Y/%m/%d') != IsToday()
dirty-equals-0.10.0/tests/test_dict.py 0000664 0000000 0000000 00000011622 15063277303 0017721 0 ustar 00root root 0000000 0000000 import pytest
from dirty_equals import IsDict, IsIgnoreDict, IsPartialDict, IsPositiveInt, IsStr, IsStrictDict
@pytest.mark.parametrize(
'input_value,expected',
[
({}, IsDict),
({}, IsDict()),
({'a': 1}, IsDict(a=1)),
({1: 2}, IsDict({1: 2})),
({'a': 1, 'b': 2}, IsDict(a=1, b=2)),
({'b': 2, 'a': 1}, IsDict(a=1, b=2)),
({'a': 1, 'b': None}, IsDict(a=1, b=None)),
({'a': 1, 'b': 3}, ~IsDict(a=1, b=2)),
# partial dict
({1: 10, 2: 20}, IsPartialDict({1: 10})),
({1: 10}, IsPartialDict({1: 10})),
({1: 10, 2: 20}, IsPartialDict({1: 10})),
({1: 10, 2: 20}, IsDict({1: 10}).settings(partial=True)),
({1: 10}, ~IsPartialDict({1: 10, 2: 20})),
({1: 10, 2: None}, ~IsPartialDict({1: 10, 2: 20})),
# ignore dict
({}, IsIgnoreDict()),
({'a': 1, 'b': 2}, IsIgnoreDict(a=1, b=2)),
({'a': 1, 'b': None}, IsIgnoreDict(a=1)),
({1: 10, 2: None}, IsIgnoreDict({1: 10})),
({'a': 1, 'b': 2}, ~IsIgnoreDict(a=1)),
({1: 10, 2: False}, ~IsIgnoreDict({1: 10})),
({1: 10, 2: False}, IsIgnoreDict({1: 10}).settings(ignore={False})),
# strict dict
({}, IsStrictDict()),
({'a': 1, 'b': 2}, IsStrictDict(a=1, b=2)),
({'a': 1, 'b': 2}, ~IsStrictDict(b=2, a=1)),
({1: 10, 2: 20}, IsStrictDict({1: 10, 2: 20})),
({1: 10, 2: 20}, ~IsStrictDict({2: 20, 1: 10})),
({1: 10, 2: 20}, ~IsDict({2: 20, 1: 10}).settings(strict=True)),
# combining types
({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, c=3).settings(partial=True)),
({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, b=2).settings(partial=True)),
({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(b=2, c=3).settings(partial=True)),
({'a': 1, 'c': 3, 'b': 2}, ~IsStrictDict(b=2, c=3).settings(partial=True)),
],
)
def test_is_dict(input_value, expected):
assert input_value == expected
def test_ne_repr_partial_dict():
v = IsPartialDict({1: 10, 2: 20})
with pytest.raises(AssertionError):
assert 1 == v
assert str(v) == 'IsPartialDict(1=10, 2=20)'
def test_ne_repr_strict_dict():
v = IsStrictDict({1: 10, 2: 20})
with pytest.raises(AssertionError):
assert 1 == v
assert str(v) == 'IsStrictDict(1=10, 2=20)'
def test_args_and_kwargs():
with pytest.raises(TypeError, match='IsDict requires either a single argument or kwargs, not both'):
IsDict(1, x=4)
def test_multiple_args():
with pytest.raises(TypeError, match='IsDict expected at most 1 argument, got 2'):
IsDict(1, 2)
def test_arg_not_dict():
with pytest.raises(TypeError, match="expected_values must be a dict, got "):
IsDict(1)
def test_combine_partial_ignore():
d = IsPartialDict(a=2, b=2, c=3)
with pytest.raises(TypeError, match='partial and ignore cannot be used together'):
d.settings(ignore={1})
def ignore_42(value):
return value == 42
def test_callable_ignore():
assert {'a': 1} == IsDict(a=1).settings(ignore=ignore_42)
assert {'a': 1, 'b': 42} == IsDict(a=1).settings(ignore=ignore_42)
assert {'a': 1, 'b': 43} != IsDict(a=1).settings(ignore=ignore_42)
@pytest.mark.parametrize(
'd,expected_repr',
[
(IsDict, 'IsDict'),
(IsDict(), 'IsDict()'),
(IsPartialDict, 'IsPartialDict'),
(IsPartialDict(), 'IsPartialDict()'),
(IsDict().settings(partial=True), 'IsDict[partial=True]()'),
(IsIgnoreDict(), 'IsIgnoreDict()'),
(IsIgnoreDict().settings(ignore={7}), 'IsIgnoreDict[ignore={7}]()'),
(IsIgnoreDict().settings(ignore={None}), 'IsIgnoreDict()'),
(IsIgnoreDict().settings(ignore=None), 'IsIgnoreDict[ignore=None]()'),
(IsDict().settings(ignore=ignore_42), 'IsDict[ignore=ignore_42]()'),
(IsDict().settings(ignore={7}), 'IsDict[ignore={7}]()'),
(IsDict().settings(ignore={None}), 'IsDict[ignore={None}]()'),
(IsPartialDict().settings(partial=False), 'IsPartialDict[partial=False]()'),
(IsStrictDict, 'IsStrictDict'),
(IsStrictDict(), 'IsStrictDict()'),
(IsDict().settings(strict=True), 'IsDict[strict=True]()'),
(IsStrictDict().settings(strict=False), 'IsStrictDict[strict=False]()'),
],
)
def test_not_equals_repr(d, expected_repr):
assert repr(d) == expected_repr
def test_ignore():
def custom_ignore(v: int) -> bool:
return v % 2 == 0
assert {'a': 1, 'b': 2, 'c': 3, 'd': 4} == IsDict(a=1, c=3).settings(ignore=custom_ignore)
def test_ignore_with_is_str():
api_data = {'id': 123, 'token': 't-abc123', 'dob': None, 'street_address': None}
token_is_str = IsStr(regex=r't\-.+')
assert api_data == IsIgnoreDict(id=IsPositiveInt, token=token_is_str)
assert token_is_str.value == 't-abc123'
def test_unhashable_value():
a = {'a': 1}
api_data = {'b': a, 'c': None}
assert api_data == IsIgnoreDict(b=a)
dirty-equals-0.10.0/tests/test_docs.py 0000664 0000000 0000000 00000002743 15063277303 0017732 0 ustar 00root root 0000000 0000000 import platform
import sys
from pathlib import Path
import pytest
from pytest_examples import CodeExample, EvalExample, find_examples
root_dir = Path(__file__).parent.parent
examples = find_examples(
root_dir / 'dirty_equals',
root_dir / 'docs',
)
@pytest.mark.skipif(platform.python_implementation() == 'PyPy', reason='PyPy does not allow metaclass dunder methods')
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="pytest-examples doesn't yet support 3.12")
@pytest.mark.parametrize('example', examples, ids=str)
def test_docstrings(example: CodeExample, eval_example: EvalExample):
prefix_settings = example.prefix_settings()
# E711 and E712 refer to `== True` and `== None` and need to be ignored
# I001 refers is a problem with black and ruff disagreeing about blank lines :shrug:
eval_example.set_config(ruff_ignore=['E711', 'E712', 'I001'])
requires = prefix_settings.get('requires')
if requires:
requires_version = tuple(int(v) for v in requires.split('.'))
if sys.version_info < requires_version:
pytest.skip(f'requires python {requires}')
if prefix_settings.get('test') != 'skip':
if eval_example.update_examples:
eval_example.run_print_update(example)
else:
eval_example.run_print_check(example)
if prefix_settings.get('lint') != 'skip':
if eval_example.update_examples:
eval_example.format(example)
else:
eval_example.lint(example)
dirty-equals-0.10.0/tests/test_inspection.py 0000664 0000000 0000000 00000004777 15063277303 0021166 0 ustar 00root root 0000000 0000000 import pytest
from dirty_equals import AnyThing, HasAttributes, HasName, HasRepr, IsInstance, IsInt, IsStr
class Foo:
def __init__(self, a=1, b=2):
self.a = a
self.b = b
def spam(self):
pass
def dirty_repr(value):
if hasattr(value, 'equals'):
return repr(value)
return ''
def test_is_instance_of():
assert Foo() == IsInstance(Foo)
assert Foo() == IsInstance[Foo]
assert 1 != IsInstance[Foo]
class Bar(Foo):
def __repr__(self):
return f'Bar(a={self.a}, b={self.b})'
def test_is_instance_of_inherit():
assert Bar() == IsInstance(Foo)
assert Foo() == IsInstance(Foo, only_direct_instance=True)
assert Bar() != IsInstance(Foo, only_direct_instance=True)
assert Foo != IsInstance(Foo)
assert Bar != IsInstance(Foo)
assert type != IsInstance(Foo)
def test_is_instance_of_repr():
assert repr(IsInstance) == 'IsInstance'
assert repr(IsInstance(Foo)) == "IsInstance()"
def even(x):
return x % 2 == 0
@pytest.mark.parametrize(
'value,dirty',
[
(Foo, HasName('Foo')),
(Foo, HasName['Foo']),
(Foo(), HasName('Foo')),
(Foo(), ~HasName('Foo', allow_instances=False)),
(Bar, ~HasName('Foo')),
(int, HasName('int')),
(42, HasName('int')),
(even, HasName('even')),
(Foo().spam, HasName('spam')),
(Foo.spam, HasName('spam')),
(Foo, HasName(IsStr(regex='F..'))),
(Bar, ~HasName(IsStr(regex='F..'))),
],
ids=dirty_repr,
)
def test_has_name(value, dirty):
assert value == dirty
@pytest.mark.parametrize(
'value,dirty',
[
(Foo(1, 2), HasAttributes(a=1, b=2)),
(Foo(1, 's'), HasAttributes(a=IsInt(), b=IsStr())),
(Foo(1, 2), ~HasAttributes(a=IsInt(), b=IsStr())),
(Foo(1, 2), ~HasAttributes(a=1, b=2, c=3)),
(Foo(1, 2), ~HasAttributes(a=1, b=2, missing=AnyThing)),
],
ids=dirty_repr,
)
def test_has_attributes(value, dirty):
assert value == dirty
@pytest.mark.parametrize(
'value,dirty',
[
(Bar(1, 2), HasRepr('Bar(a=1, b=2)')),
(Bar(1, 2), HasRepr['Bar(a=1, b=2)']),
(4, ~HasRepr('Bar(a=1, b=2)')),
(Foo(), HasRepr(IsStr(regex=r''))),
(Foo, HasRepr("")),
(42, HasRepr('42')),
(43, ~HasRepr('42')),
],
ids=dirty_repr,
)
def test_has_repr(value, dirty):
assert value == dirty
dirty-equals-0.10.0/tests/test_list_tuple.py 0000664 0000000 0000000 00000010443 15063277303 0021162 0 ustar 00root root 0000000 0000000 import pytest
from dirty_equals import AnyThing, Contains, HasLen, IsInt, IsList, IsListOrTuple, IsNegative, IsTuple
@pytest.mark.parametrize(
'other,dirty',
[
([], IsList),
((), IsTuple),
([], IsList()),
([1], IsList(length=1)),
((), IsTuple()),
([1, 2, 3], IsList(1, 2, 3)),
((1, 2, 3), IsTuple(1, 2, 3)),
((1, 2, 3), IsListOrTuple(1, 2, 3)),
([1, 2, 3], IsListOrTuple(1, 2, 3)),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=5)),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=(4, 6))),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=[4, 6])),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=(4, ...))),
([3, 2, 1], IsList(1, 2, 3, check_order=False)),
([{1: 2}, 7], IsList(7, {1: 2}, check_order=False)),
([1, 2, 3, 4], IsList(positions={0: 1, 2: 3, -1: 4})),
([1, 2, 3], IsList(AnyThing, 2, 3)),
([1, 2, 3], IsList(1, 2, IsInt)),
([3, 2, 1], IsList(1, 2, IsInt, check_order=False)),
([1, 2, 2], IsList(2, 2, 1, check_order=False)),
([], HasLen(0)),
([1, 2, 3], HasLen(3)),
('123', HasLen(3)),
(b'123', HasLen(3)),
({'a': 1, 'b': 2, 'c': 3}, HasLen(3)),
([1, 2], HasLen(1, 2)),
([1, 2], HasLen(2, 3)),
([1, 2, 3], HasLen(2, ...)),
([1, 2, 3], HasLen(0, ...)),
([1, 2, 3], Contains(1)),
([1, 2, 3], Contains(1, 2)),
((1, 2, 3), Contains(1)),
({1, 2, 3}, Contains(1)),
('abc', Contains('b')),
({'a': 1, 'b': 2}, Contains('a')),
([{'a': 1}, {'b': 2}], Contains({'a': 1})),
],
)
def test_dirty_equals(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
([], IsTuple),
((), IsList),
([1], IsList),
([1, 2, 3], IsTuple(1, 2, 3)),
((1, 2, 3), IsList(1, 2, 3)),
([1, 2, 3, 4], IsList(1, 2, 3)),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=6)),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=(6, 8))),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=(0, 2))),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=[1, 2])),
([1, 2, 3, 4, 5], IsList(1, 2, 3, length=(6, ...))),
([3, 2, 1, 4], IsList(1, 2, 3, check_order=False)),
([1, 2, 3, 4], IsList(positions={0: 1, 2: 3, -1: 5})),
([1, 2, 3], IsList(1, 2, IsNegative)),
([1, 2, 2], IsList(1, 2, 3, check_order=False)),
([1, 2, 3], IsList(1, 2, 2, check_order=False)),
([1], HasLen(0)),
([], HasLen(1)),
('abc', HasLen(2)),
([1, 2, 3], HasLen(1, 2)),
([1], HasLen(2, 3)),
([1], HasLen(2, ...)),
(123, HasLen(0, ...)),
([1, 2, 3], Contains(10)),
([1, 2, 3], Contains(1, 'a')),
([1, 2, 3], Contains(1, 'a')),
([{'a': 1}, {'b': 2}], Contains({'a': 2})),
({1, 2, 3}, Contains({1: 2})),
],
)
def test_dirty_not_equals(other, dirty):
assert other != dirty
def test_args_and_positions():
with pytest.raises(TypeError, match='IsList requires either args or positions, not both'):
IsList(1, 2, positions={0: 1})
def test_positions_with_check_order():
with pytest.raises(TypeError, match='check_order=False is not compatible with positions'):
IsList(check_order=False, positions={0: 1})
def test_wrong_length_length():
with pytest.raises(TypeError, match='length must be a tuple of length 2, not 3'):
IsList(1, 2, length=(1, 2, 3))
@pytest.mark.parametrize(
'dirty,repr_str',
[
(IsList, 'IsList'),
(IsTuple(1, 2, 3), 'IsTuple(1, 2, 3)'),
(IsList(positions={1: 10, 2: 20}), 'IsList(positions={1: 10, 2: 20})'),
(IsTuple(1, 2, 3, length=4), 'IsTuple(1, 2, 3, length=4)'),
(IsTuple(1, 2, 3, length=(6, ...)), 'IsTuple(1, 2, 3, length=(6, ...))'),
(IsTuple(1, 2, 3, length=(6, 'x')), 'IsTuple(1, 2, 3, length=(6, ...))'),
(IsTuple(1, 2, 3, length=(6, 10)), 'IsTuple(1, 2, 3, length=(6, 10))'),
(IsTuple(1, 2, 3, check_order=False), 'IsTuple(1, 2, 3, check_order=False)'),
(HasLen(42), 'HasLen(42)'),
(HasLen(0, ...), 'HasLen(0, ...)'),
],
)
def test_repr(dirty, repr_str):
assert repr(dirty) == repr_str
def test_no_contains_value():
with pytest.raises(TypeError):
Contains()
dirty-equals-0.10.0/tests/test_numeric.py 0000664 0000000 0000000 00000007216 15063277303 0020444 0 ustar 00root root 0000000 0000000 import pytest
from dirty_equals import (
IsApprox,
IsFloat,
IsFloatInf,
IsFloatInfNeg,
IsFloatInfPos,
IsFloatNan,
IsInt,
IsNegative,
IsNegativeFloat,
IsNegativeInt,
IsNonNegative,
IsNonPositive,
IsPositive,
IsPositiveFloat,
IsPositiveInt,
)
@pytest.mark.parametrize(
'other,dirty',
[
(1, IsInt),
(1, IsInt()),
(1, IsInt(exactly=1)),
(1, IsPositiveInt),
(-1, IsNegativeInt),
(-1.0, IsFloat),
(-1.0, IsFloat(exactly=-1.0)),
(1.0, IsPositiveFloat),
(-1.0, IsNegativeFloat),
(1, IsPositive),
(1.0, IsPositive),
(-1, IsNegative),
(-1.0, IsNegative),
(5, IsInt(gt=4)),
(5, IsInt(ge=5)),
(5, IsInt(lt=6)),
(5, IsInt(le=5)),
(1, IsApprox(1)),
(1, IsApprox(2, delta=1)),
(100, IsApprox(99)),
(-100, IsApprox(-99)),
(0, IsNonNegative),
(1, IsNonNegative),
(0.0, IsNonNegative),
(1.0, IsNonNegative),
(0, IsNonPositive),
(-1, IsNonPositive),
(0.0, IsNonPositive),
(-1.0, IsNonPositive),
(-1, IsNonPositive & IsInt),
(1, IsNonNegative & IsInt),
(float('inf'), IsFloatInf),
(-float('inf'), IsFloatInf),
(float('-inf'), IsFloatInf),
(float('inf'), IsFloatInfPos),
(-float('-inf'), IsFloatInfPos),
(-float('inf'), IsFloatInfNeg),
(float('-inf'), IsFloatInfNeg),
(float('nan'), IsFloatNan),
(-float('nan'), IsFloatNan),
(float('-nan'), IsFloatNan),
],
)
def test_dirty_equals(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
(1.0, IsInt),
(1.2, IsInt),
(1, IsInt(exactly=2)),
(True, IsInt),
(False, IsInt),
(1.0, IsInt()),
(-1, IsPositiveInt),
(0, IsPositiveInt),
(1, IsNegativeInt),
(0, IsNegativeInt),
(1, IsFloat),
(1, IsFloat(exactly=1.0)),
(1.1234, IsFloat(exactly=1.0)),
(-1.0, IsPositiveFloat),
(0.0, IsPositiveFloat),
(1.0, IsNegativeFloat),
(0.0, IsNegativeFloat),
(-1, IsPositive),
(-1.0, IsPositive),
(4, IsInt(gt=4)),
(4, IsInt(ge=5)),
(6, IsInt(lt=6)),
(6, IsInt(le=5)),
(-1, IsNonNegative),
(-1.0, IsNonNegative),
(1, IsNonPositive),
(1.0, IsNonPositive),
(-1.0, IsNonPositive & IsInt),
(1.0, IsNonNegative & IsInt),
(1, IsFloatNan),
(1.0, IsFloatNan),
(1, IsFloatInf),
(1.0, IsFloatInf),
(-float('inf'), IsFloatInfPos),
(float('-inf'), IsFloatInfPos),
(-float('-inf'), IsFloatInfNeg),
(-float('-inf'), IsFloatInfNeg),
],
ids=repr,
)
def test_dirty_not_equals(other, dirty):
assert other != dirty
def test_invalid_approx_gt():
with pytest.raises(TypeError, match='"approx" cannot be combined with "gt", "lt", "ge", or "le"'):
IsInt(approx=1, gt=1)
def test_invalid_exactly_approx():
with pytest.raises(TypeError, match='"exactly" cannot be combined with "approx"'):
IsInt(exactly=1, approx=1)
def test_invalid_exactly_gt():
with pytest.raises(TypeError, match='"exactly" cannot be combined with "gt", "lt", "ge", or "le"'):
IsInt(exactly=1, gt=1)
def test_not_int():
d = IsInt()
with pytest.raises(AssertionError):
assert '1' == d
assert repr(d) == 'IsInt()'
def test_not_negative():
d = IsNegativeInt
with pytest.raises(AssertionError):
assert 1 == d
assert repr(d) == 'IsNegativeInt'
dirty-equals-0.10.0/tests/test_other.py 0000664 0000000 0000000 00000024724 15063277303 0020126 0 ustar 00root root 0000000 0000000 import uuid
from dataclasses import dataclass
from enum import Enum, auto
from hashlib import md5, sha1, sha256
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
import pytest
from dirty_equals import (
FunctionCheck,
IsDataclass,
IsDataclassType,
IsEnum,
IsHash,
IsInt,
IsIP,
IsJson,
IsPartialDataclass,
IsStr,
IsStrictDataclass,
IsUrl,
IsUUID,
)
class FooEnum(Enum):
a = auto()
b = auto()
c = 'c'
@dataclass
class Foo:
a: int
b: int
c: str
foo = Foo(1, 2, 'c')
@pytest.mark.parametrize(
'other,dirty',
[
(uuid.uuid4(), IsUUID()),
(uuid.uuid4(), IsUUID),
(uuid.uuid4(), IsUUID(4)),
('edf9f29e-45c7-431c-99db-28ea44df9785', IsUUID),
('edf9f29e-45c7-431c-99db-28ea44df9785', IsUUID(4)),
('edf9f29e45c7431c99db28ea44df9785', IsUUID(4)),
(uuid.uuid3(uuid.UUID('edf9f29e-45c7-431c-99db-28ea44df9785'), 'abc'), IsUUID),
(uuid.uuid3(uuid.UUID('edf9f29e-45c7-431c-99db-28ea44df9785'), 'abc'), IsUUID(3)),
(uuid.uuid1(), IsUUID(1)),
(str(uuid.uuid1()), IsUUID(1)),
('ea9e828d-fd18-3898-99f3-5a46dbcee036', IsUUID(3)),
],
)
def test_is_uuid_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
('foobar', IsUUID()),
([1, 2, 3], IsUUID()),
('edf9f29e-45c7-431c-99db-28ea44df9785', IsUUID(5)),
(uuid.uuid3(uuid.UUID('edf9f29e-45c7-431c-99db-28ea44df9785'), 'abc'), IsUUID(4)),
(uuid.uuid1(), IsUUID(4)),
('edf9f29e-45c7-431c-99db-28ea44df9785', IsUUID(1)),
('ea9e828d-fd18-3898-99f3-5a46dbcee036', IsUUID(4)),
],
)
def test_is_uuid_false(other, dirty):
assert other != dirty
def test_is_uuid_false_repr():
is_uuid = IsUUID()
with pytest.raises(AssertionError):
assert '123' == is_uuid
assert str(is_uuid) == 'IsUUID()'
def test_is_uuid4_false_repr():
is_uuid = IsUUID(4)
with pytest.raises(AssertionError):
assert '123' == is_uuid
assert str(is_uuid) == 'IsUUID(4)'
@pytest.mark.parametrize('json_value', ['null', '"xyz"', '[1, 2, 3]', '{"a": 1}'])
def test_is_json_any_true(json_value):
assert json_value == IsJson()
assert json_value == IsJson
def test_is_json_any_false():
is_json = IsJson()
with pytest.raises(AssertionError):
assert 'foobar' == is_json
assert str(is_json) == 'IsJson()'
@pytest.mark.parametrize(
'json_value,expected_value',
[
('null', None),
('"xyz"', 'xyz'),
('[1, 2, 3]', [1, 2, 3]),
('{"a": 1}', {'a': 1}),
],
)
def test_is_json_specific_true(json_value, expected_value):
assert json_value == IsJson(expected_value)
assert json_value == IsJson[expected_value]
def test_is_json_invalid():
assert 'invalid json' != IsJson
assert 123 != IsJson
assert [1, 2] != IsJson
def test_is_json_kwargs():
assert '{"a": 1, "b": 2}' == IsJson(a=1, b=2)
assert '{"a": 1, "b": 3}' != IsJson(a=1, b=2)
def test_is_json_specific_false():
is_json = IsJson([1, 2, 3])
with pytest.raises(AssertionError):
assert '{"a": 1}' == is_json
assert str(is_json) == 'IsJson([1, 2, 3])'
def test_equals_function():
func_argument = None
def foo(v):
nonlocal func_argument
func_argument = v
return v % 2 == 0
assert 4 == FunctionCheck(foo)
assert func_argument == 4
assert 5 != FunctionCheck(foo)
def test_equals_function_fail():
def foobar(v):
return False
c = FunctionCheck(foobar)
with pytest.raises(AssertionError):
assert 4 == c
assert str(c) == 'FunctionCheck(foobar)'
def test_json_both():
with pytest.raises(TypeError, match='IsJson requires either an argument or kwargs, not both'):
IsJson(1, a=2)
@pytest.mark.parametrize(
'other,dirty',
[
(IPv4Address('127.0.0.1'), IsIP()),
(IPv4Network('43.48.0.0/12'), IsIP()),
(IPv6Address('::eeff:ae3f:d473'), IsIP()),
(IPv6Network('::eeff:ae3f:d473/128'), IsIP()),
('2001:0db8:0a0b:12f0:0000:0000:0000:0001', IsIP()),
('179.27.154.96', IsIP),
('43.62.123.119', IsIP(version=4)),
('::ffff:2b3e:7b77', IsIP(version=6)),
('0:0:0:0:0:ffff:2b3e:7b77', IsIP(version=6)),
('54.43.53.219/10', IsIP(version=4, netmask='255.192.0.0')),
('::ffff:aebf:d473/12', IsIP(version=6, netmask='fff0::')),
('2001:0db8:0a0b:12f0:0000:0000:0000:0001', IsIP(version=6)),
(3232235521, IsIP()),
(b'\xc0\xa8\x00\x01', IsIP()),
(338288524927261089654018896845572831328, IsIP(version=6)),
(b'\x20\x01\x06\x58\x02\x2a\xca\xfe\x02\x00\x00\x00\x00\x00\x00\x01', IsIP(version=6)),
],
)
def test_is_ip_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
('foobar', IsIP()),
([1, 2, 3], IsIP()),
('210.115.28.193', IsIP(version=6)),
('::ffff:d273:1cc1', IsIP(version=4)),
('210.115.28.193/12', IsIP(version=6, netmask='255.255.255.0')),
('::ffff:d273:1cc1', IsIP(version=6, netmask='fff0::')),
(3232235521, IsIP(version=6)),
(338288524927261089654018896845572831328, IsIP(version=4)),
],
)
def test_is_ip_false(other, dirty):
assert other != dirty
def test_not_ip_repr():
is_ip = IsIP()
with pytest.raises(AssertionError):
assert '123' == is_ip
assert str(is_ip) == 'IsIP()'
def test_ip_bad_netmask():
with pytest.raises(TypeError, match='To check the netmask you must specify the IP version'):
IsIP(netmask='255.255.255.0')
@pytest.mark.parametrize(
'other,dirty',
[
('f1e069787ECE74531d112559945c6871', IsHash('md5')),
('40bd001563085fc35165329ea1FF5c5ecbdbbeef', IsHash('sha-1')),
('a665a45920422f9d417e4867eFDC4fb8a04a1f3fff1fa07e998e86f7f7a27ae3', IsHash('sha-256')),
(b'f1e069787ECE74531d112559945c6871', IsHash('md5')),
(bytearray(b'f1e069787ECE74531d112559945c6871'), IsHash('md5')),
],
)
def test_is_hash_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
('foobar', IsHash('md5')),
(b'\x81 UnicodeDecodeError', IsHash('md5')),
([1, 2, 3], IsHash('sha-1')),
('f1e069787ECE74531d112559945c6871d', IsHash('md5')),
('400bd001563085fc35165329ea1FF5c5ecbdbbeef', IsHash('sha-1')),
('a665a45920422g9d417e4867eFDC4fb8a04a1f3fff1fa07e998e86f7f7a27ae3', IsHash('sha-256')),
],
)
def test_is_hash_false(other, dirty):
assert other != dirty
@pytest.mark.parametrize(
'hash_type',
['md5', 'sha-1', 'sha-256'],
)
def test_is_hash_md5_false_repr(hash_type):
is_hash = IsHash(hash_type)
with pytest.raises(AssertionError):
assert '123' == is_hash
assert str(is_hash) == f"IsHash('{hash_type}')"
@pytest.mark.parametrize(
'hash_func, hash_type',
[(md5, 'md5'), (sha1, 'sha-1'), (sha256, 'sha-256')],
)
def test_hashlib_hashes(hash_func, hash_type):
assert hash_func(b'dirty equals').hexdigest() == IsHash(hash_type)
def test_wrong_hash_type():
with pytest.raises(ValueError, match='Hash type must be one of the following values: md5, sha-1, sha-256'):
assert '123' == IsHash('ntlm')
@pytest.mark.parametrize(
'other,dirty',
[
('https://example.com', IsUrl),
('https://example.com', IsUrl(scheme='https')),
('postgres://user:pass@localhost:5432/app', IsUrl(postgres_dsn=True)),
],
)
def test_is_url_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
('https://example.com', IsUrl(postgres_dsn=True)),
('https://example.com', IsUrl(scheme='http')),
('definitely not a url', IsUrl),
(42, IsUrl),
('https://anotherexample.com', IsUrl(postgres_dsn=True)),
],
)
def test_is_url_false(other, dirty):
assert other != dirty
def test_is_url_invalid_kwargs():
with pytest.raises(
TypeError,
match='IsURL only checks these attributes: scheme, host, host_type, user, password, port, path, query, '
'fragment',
):
IsUrl(https=True)
def test_is_url_too_many_url_types():
with pytest.raises(
ValueError,
match='You can only check against one Pydantic url type at a time',
):
assert 'https://example.com' == IsUrl(any_url=True, http_url=True, postgres_dsn=True)
@pytest.mark.parametrize(
'other,dirty',
[
(Foo, IsDataclassType),
(Foo, IsDataclassType()),
],
)
def test_is_dataclass_type_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
(foo, IsDataclassType),
(foo, IsDataclassType()),
(Foo, IsDataclass),
],
)
def test_is_dataclass_type_false(other, dirty):
assert other != dirty
@pytest.mark.parametrize(
'other,dirty',
[
(foo, IsDataclass),
(foo, IsDataclass()),
(foo, IsDataclass(a=IsInt, b=2, c=IsStr)),
(foo, IsDataclass(a=1, c='c', b=2).settings(strict=False, partial=False)),
(foo, IsDataclass(a=1, b=2, c='c').settings(strict=True, partial=False)),
(foo, IsStrictDataclass(a=1, b=2, c='c')),
(foo, IsDataclass(c='c', a=1).settings(strict=False, partial=True)),
(foo, IsPartialDataclass(c='c', a=1)),
(foo, IsDataclass(b=2, c='c').settings(strict=True, partial=True)),
(foo, IsStrictDataclass(b=2, c='c').settings(partial=True)),
(foo, IsPartialDataclass(b=2, c='c').settings(strict=True)),
],
)
def test_is_dataclass_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
(foo, IsDataclassType),
(Foo, IsDataclass),
(foo, IsDataclass(a=1)),
(foo, IsDataclass(a=IsStr, b=IsInt, c=IsStr)),
(foo, IsDataclass(b=2, a=1, c='c').settings(strict=True)),
(foo, IsStrictDataclass(b=2, a=1, c='c')),
(foo, IsDataclass(b=2, a=1).settings(partial=False)),
],
)
def test_is_dataclass_false(other, dirty):
assert other != dirty
@pytest.mark.parametrize(
'other,dirty',
[
(FooEnum.a, IsEnum),
(FooEnum.b, IsEnum(FooEnum)),
(2, IsEnum(FooEnum)),
('c', IsEnum(FooEnum)),
],
)
def test_is_enum_true(other, dirty):
assert other == dirty
@pytest.mark.parametrize(
'other,dirty',
[
(FooEnum, IsEnum),
(FooEnum, IsEnum(FooEnum)),
(4, IsEnum(FooEnum)),
],
)
def test_is_enum_false(other, dirty):
assert other != dirty
dirty-equals-0.10.0/tests/test_strings.py 0000664 0000000 0000000 00000010234 15063277303 0020465 0 ustar 00root root 0000000 0000000 import re
import pytest
from dirty_equals import IsAnyStr, IsBytes, IsStr
@pytest.mark.parametrize(
'value,dirty,match',
[
# IsStr tests
('foo', IsStr, True),
('foo', IsStr(), True),
(b'foo', IsStr, False),
('foo', IsStr(regex='fo{2}'), True),
('foo', IsStr(regex=b'fo{2}'), False),
('foo', IsStr(regex=re.compile('fo{2}')), True),
('Foo', IsStr(regex=re.compile('fo{2}', flags=re.I)), True),
('Foo', IsStr(regex=re.compile('fo{2}'), regex_flags=re.I), True),
('foo', IsStr(regex='fo'), False),
('foo', IsStr(regex='foo', max_length=2), False),
('foo\nbar', IsStr(regex='fo.*', regex_flags=re.S), True),
('foo\nbar', IsStr(regex='fo.*'), False),
('foo', IsStr(min_length=3), True),
('fo', IsStr(min_length=3), False),
('foo', IsStr(max_length=3), True),
('foobar', IsStr(max_length=3), False),
('foo', IsStr(case='lower'), True),
('FOO', IsStr(case='lower'), False),
('FOO', IsStr(case='upper'), True),
('foo', IsStr(case='upper'), False),
# IsBytes tests
(b'foo', IsBytes, True),
(b'foo', IsBytes(), True),
('foo', IsBytes, False),
(b'foo', IsBytes(regex=b'fo{2}'), True),
(b'Foo', IsBytes(regex=re.compile(b'fo{2}', flags=re.I)), True),
(b'Foo', IsBytes(regex=re.compile(b'fo{2}'), regex_flags=re.I), True),
(b'foo', IsBytes(regex=b'fo'), False),
(b'foo', IsBytes(regex=b'foo', max_length=2), False),
(b'foo\nbar', IsBytes(regex=b'fo.*', regex_flags=re.S), True),
(b'foo\nbar', IsBytes(regex=b'fo.*'), False),
(b'foo', IsBytes(min_length=3), True),
(b'fo', IsBytes(min_length=3), False),
# IsAnyStr tests
(b'foo', IsAnyStr, True),
(b'foo', IsAnyStr(), True),
('foo', IsAnyStr, True),
(b'foo', IsAnyStr(regex=b'fo{2}'), True),
('foo', IsAnyStr(regex=b'fo{2}'), True),
(b'foo', IsAnyStr(regex='fo{2}'), True),
('foo', IsAnyStr(regex='fo{2}'), True),
(b'Foo', IsAnyStr(regex=re.compile(b'fo{2}', flags=re.I)), True),
('Foo', IsAnyStr(regex=re.compile(b'fo{2}', flags=re.I)), True),
(b'Foo', IsAnyStr(regex=re.compile(b'fo{2}'), regex_flags=re.I), True),
('Foo', IsAnyStr(regex=re.compile(b'fo{2}'), regex_flags=re.I), True),
(b'Foo', IsAnyStr(regex=re.compile('fo{2}', flags=re.I)), True),
('Foo', IsAnyStr(regex=re.compile('fo{2}', flags=re.I)), True),
(b'Foo', IsAnyStr(regex=re.compile('fo{2}'), regex_flags=re.I), True),
('Foo', IsAnyStr(regex=re.compile('fo{2}'), regex_flags=re.I), True),
(b'foo\nbar', IsAnyStr(regex=b'fo.*', regex_flags=re.S), True),
(b'foo\nbar', IsAnyStr(regex=b'fo.*'), False),
('foo', IsAnyStr(regex=b'foo', max_length=2), False),
(b'foo', IsAnyStr(regex=b'foo', max_length=2), False),
(b'foo', IsAnyStr(min_length=3), True),
('foo', IsAnyStr(min_length=3), True),
(b'fo', IsAnyStr(min_length=3), False),
],
)
def test_dirty_equals_true(value, dirty, match):
if match:
assert value == dirty
else:
assert value != dirty
def test_regex_true():
assert 'whatever' == IsStr(regex='whatever')
reg = IsStr(regex='wh.*er')
assert 'whatever' == reg
assert str(reg) == "'whatever'"
def test_regex_bytes_true():
assert b'whatever' == IsBytes(regex=b'whatever')
assert b'whatever' == IsBytes(regex=b'wh.*er')
def test_regex_false():
reg = IsStr(regex='wh.*er')
with pytest.raises(AssertionError):
assert 'WHATEVER' == reg
assert str(reg) == "IsStr(regex='wh.*er')"
def test_regex_false_type_error():
assert 123 != IsStr(regex='wh.*er')
reg = IsBytes(regex=b'wh.*er')
with pytest.raises(AssertionError):
assert 'whatever' == reg
assert str(reg) == "IsBytes(regex=b'wh.*er')"
def test_is_any_str():
assert 'foobar' == IsAnyStr
assert b'foobar' == IsAnyStr
assert 123 != IsAnyStr
assert 'foo' == IsAnyStr(regex='foo')
assert 'foo' == IsAnyStr(regex=b'foo')
assert b'foo' == IsAnyStr(regex='foo')
assert b'foo' == IsAnyStr(regex=b'foo')
dirty-equals-0.10.0/uv.lock 0000664 0000000 0000000 00000741454 15063277303 0015544 0 ustar 00root root 0000000 0000000 version = 1
revision = 3
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version < '3.10'",
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "babel"
version = "2.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
]
[[package]]
name = "black"
version = "25.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/40/dbe31fc56b218a858c8fc6f5d8d3ba61c1fa7e989d43d4a4574b8b992840/black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7", size = 1715605, upload-time = "2025-09-19T00:36:13.483Z" },
{ url = "https://files.pythonhosted.org/packages/92/b2/f46800621200eab6479b1f4c0e3ede5b4c06b768e79ee228bc80270bcc74/black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92", size = 1571829, upload-time = "2025-09-19T00:32:42.13Z" },
{ url = "https://files.pythonhosted.org/packages/4e/64/5c7f66bd65af5c19b4ea86062bb585adc28d51d37babf70969e804dbd5c2/black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713", size = 1631888, upload-time = "2025-09-19T00:30:54.212Z" },
{ url = "https://files.pythonhosted.org/packages/3b/64/0b9e5bfcf67db25a6eef6d9be6726499a8a72ebab3888c2de135190853d3/black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1", size = 1327056, upload-time = "2025-09-19T00:31:08.877Z" },
{ url = "https://files.pythonhosted.org/packages/b7/f4/7531d4a336d2d4ac6cc101662184c8e7d068b548d35d874415ed9f4116ef/black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa", size = 1698727, upload-time = "2025-09-19T00:31:14.264Z" },
{ url = "https://files.pythonhosted.org/packages/28/f9/66f26bfbbf84b949cc77a41a43e138d83b109502cd9c52dfc94070ca51f2/black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d", size = 1555679, upload-time = "2025-09-19T00:31:29.265Z" },
{ url = "https://files.pythonhosted.org/packages/bf/59/61475115906052f415f518a648a9ac679d7afbc8da1c16f8fdf68a8cebed/black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608", size = 1617453, upload-time = "2025-09-19T00:30:42.24Z" },
{ url = "https://files.pythonhosted.org/packages/7f/5b/20fd5c884d14550c911e4fb1b0dae00d4abb60a4f3876b449c4d3a9141d5/black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f", size = 1333655, upload-time = "2025-09-19T00:30:56.715Z" },
{ url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012, upload-time = "2025-09-19T00:33:08.678Z" },
{ url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421, upload-time = "2025-09-19T00:35:25.937Z" },
{ url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619, upload-time = "2025-09-19T00:30:49.241Z" },
{ url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481, upload-time = "2025-09-19T00:31:29.625Z" },
{ url = "https://files.pythonhosted.org/packages/48/99/3acfea65f5e79f45472c45f87ec13037b506522719cd9d4ac86484ff51ac/black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175", size = 1742165, upload-time = "2025-09-19T00:34:10.402Z" },
{ url = "https://files.pythonhosted.org/packages/3a/18/799285282c8236a79f25d590f0222dbd6850e14b060dfaa3e720241fd772/black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f", size = 1581259, upload-time = "2025-09-19T00:32:49.685Z" },
{ url = "https://files.pythonhosted.org/packages/f1/ce/883ec4b6303acdeca93ee06b7622f1fa383c6b3765294824165d49b1a86b/black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831", size = 1655583, upload-time = "2025-09-19T00:30:44.505Z" },
{ url = "https://files.pythonhosted.org/packages/21/17/5c253aa80a0639ccc427a5c7144534b661505ae2b5a10b77ebe13fa25334/black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357", size = 1343428, upload-time = "2025-09-19T00:32:13.839Z" },
{ url = "https://files.pythonhosted.org/packages/c4/26/0f724eb152bc9fc03029a9c903ddd77a288285042222a381050d27e64ac1/black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47", size = 1715243, upload-time = "2025-09-19T00:34:14.216Z" },
{ url = "https://files.pythonhosted.org/packages/fb/be/cb986ea2f0fabd0ee58668367724ba16c3a042842e9ebe009c139f8221c9/black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823", size = 1571246, upload-time = "2025-09-19T00:31:39.624Z" },
{ url = "https://files.pythonhosted.org/packages/82/ce/74cf4d66963fca33ab710e4c5817ceeff843c45649f61f41d88694c2e5db/black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140", size = 1631265, upload-time = "2025-09-19T00:31:05.341Z" },
{ url = "https://files.pythonhosted.org/packages/ff/f3/9b11e001e84b4d1721f75e20b3c058854a748407e6fc1abe6da0aa22014f/black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933", size = 1326615, upload-time = "2025-09-19T00:31:25.347Z" },
{ url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" },
]
[[package]]
name = "certifi"
version = "2025.8.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695, upload-time = "2025-08-09T07:55:36.452Z" },
{ url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153, upload-time = "2025-08-09T07:55:38.467Z" },
{ url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428, upload-time = "2025-08-09T07:55:40.072Z" },
{ url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627, upload-time = "2025-08-09T07:55:41.706Z" },
{ url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388, upload-time = "2025-08-09T07:55:43.262Z" },
{ url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077, upload-time = "2025-08-09T07:55:44.903Z" },
{ url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631, upload-time = "2025-08-09T07:55:46.346Z" },
{ url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210, upload-time = "2025-08-09T07:55:47.539Z" },
{ url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739, upload-time = "2025-08-09T07:55:48.744Z" },
{ url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825, upload-time = "2025-08-09T07:55:50.305Z" },
{ url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452, upload-time = "2025-08-09T07:55:51.461Z" },
{ url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" },
{ url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" },
{ url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" },
{ url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" },
{ url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" },
{ url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" },
{ url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" },
{ url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" },
{ url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" },
{ url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" },
{ url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" },
{ url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" },
{ url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" },
{ url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" },
{ url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" },
{ url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" },
{ url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" },
{ url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" },
{ url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" },
{ url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" },
{ url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" },
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
{ url = "https://files.pythonhosted.org/packages/c2/ca/9a0983dd5c8e9733565cf3db4df2b0a2e9a82659fd8aa2a868ac6e4a991f/charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", size = 207520, upload-time = "2025-08-09T07:57:11.026Z" },
{ url = "https://files.pythonhosted.org/packages/39/c6/99271dc37243a4f925b09090493fb96c9333d7992c6187f5cfe5312008d2/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", size = 147307, upload-time = "2025-08-09T07:57:12.4Z" },
{ url = "https://files.pythonhosted.org/packages/e4/69/132eab043356bba06eb333cc2cc60c6340857d0a2e4ca6dc2b51312886b3/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", size = 160448, upload-time = "2025-08-09T07:57:13.712Z" },
{ url = "https://files.pythonhosted.org/packages/04/9a/914d294daa4809c57667b77470533e65def9c0be1ef8b4c1183a99170e9d/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", size = 157758, upload-time = "2025-08-09T07:57:14.979Z" },
{ url = "https://files.pythonhosted.org/packages/b0/a8/6f5bcf1bcf63cb45625f7c5cadca026121ff8a6c8a3256d8d8cd59302663/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", size = 152487, upload-time = "2025-08-09T07:57:16.332Z" },
{ url = "https://files.pythonhosted.org/packages/c4/72/d3d0e9592f4e504f9dea08b8db270821c909558c353dc3b457ed2509f2fb/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", size = 150054, upload-time = "2025-08-09T07:57:17.576Z" },
{ url = "https://files.pythonhosted.org/packages/20/30/5f64fe3981677fe63fa987b80e6c01042eb5ff653ff7cec1b7bd9268e54e/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", size = 161703, upload-time = "2025-08-09T07:57:20.012Z" },
{ url = "https://files.pythonhosted.org/packages/e1/ef/dd08b2cac9284fd59e70f7d97382c33a3d0a926e45b15fc21b3308324ffd/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", size = 159096, upload-time = "2025-08-09T07:57:21.329Z" },
{ url = "https://files.pythonhosted.org/packages/45/8c/dcef87cfc2b3f002a6478f38906f9040302c68aebe21468090e39cde1445/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", size = 153852, upload-time = "2025-08-09T07:57:22.608Z" },
{ url = "https://files.pythonhosted.org/packages/63/86/9cbd533bd37883d467fcd1bd491b3547a3532d0fbb46de2b99feeebf185e/charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", size = 99840, upload-time = "2025-08-09T07:57:23.883Z" },
{ url = "https://files.pythonhosted.org/packages/ce/d6/7e805c8e5c46ff9729c49950acc4ee0aeb55efb8b3a56687658ad10c3216/charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", size = 107438, upload-time = "2025-08-09T07:57:25.287Z" },
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
]
[[package]]
name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
]
[[package]]
name = "click"
version = "8.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
]
[[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 = "coverage"
version = "7.10.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025, upload-time = "2025-08-29T15:32:57.169Z" },
{ url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419, upload-time = "2025-08-29T15:32:59.908Z" },
{ url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180, upload-time = "2025-08-29T15:33:01.608Z" },
{ url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992, upload-time = "2025-08-29T15:33:03.239Z" },
{ url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851, upload-time = "2025-08-29T15:33:04.603Z" },
{ url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891, upload-time = "2025-08-29T15:33:06.176Z" },
{ url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909, upload-time = "2025-08-29T15:33:07.74Z" },
{ url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786, upload-time = "2025-08-29T15:33:08.965Z" },
{ url = "https://files.pythonhosted.org/packages/d0/4c/37ed872374a21813e0d3215256180c9a382c3f5ced6f2e5da0102fc2fd3e/coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", size = 219521, upload-time = "2025-08-29T15:33:10.599Z" },
{ url = "https://files.pythonhosted.org/packages/8e/36/9311352fdc551dec5b973b61f4e453227ce482985a9368305880af4f85dd/coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", size = 220417, upload-time = "2025-08-29T15:33:11.907Z" },
{ url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" },
{ url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" },
{ url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" },
{ url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" },
{ url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" },
{ url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" },
{ url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" },
{ url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" },
{ url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" },
{ url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" },
{ url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" },
{ url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" },
{ url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" },
{ url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" },
{ url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" },
{ url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" },
{ url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" },
{ url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" },
{ url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" },
{ url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" },
{ url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" },
{ url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" },
{ url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" },
{ url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" },
{ url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" },
{ url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" },
{ url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" },
{ url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" },
{ url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" },
{ url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" },
{ url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" },
{ url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" },
{ url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" },
{ url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" },
{ url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" },
{ url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" },
{ url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" },
{ url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" },
{ url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" },
{ url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" },
{ url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" },
{ url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" },
{ url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" },
{ url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" },
{ url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" },
{ url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" },
{ url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" },
{ url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" },
{ url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" },
{ url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" },
{ url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" },
{ url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" },
{ url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" },
{ url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" },
{ url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" },
{ url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" },
{ url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" },
{ url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" },
{ url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" },
{ url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" },
{ url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" },
{ url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" },
{ url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" },
{ url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" },
{ url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" },
{ url = "https://files.pythonhosted.org/packages/91/70/f73ad83b1d2fd2d5825ac58c8f551193433a7deaf9b0d00a8b69ef61cd9a/coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352", size = 217009, upload-time = "2025-08-29T15:34:57.381Z" },
{ url = "https://files.pythonhosted.org/packages/01/e8/099b55cd48922abbd4b01ddd9ffa352408614413ebfc965501e981aced6b/coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612", size = 217400, upload-time = "2025-08-29T15:34:58.985Z" },
{ url = "https://files.pythonhosted.org/packages/ee/d1/c6bac7c9e1003110a318636fef3b5c039df57ab44abcc41d43262a163c28/coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b", size = 243835, upload-time = "2025-08-29T15:35:00.541Z" },
{ url = "https://files.pythonhosted.org/packages/01/f9/82c6c061838afbd2172e773156c0aa84a901d59211b4975a4e93accf5c89/coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144", size = 245658, upload-time = "2025-08-29T15:35:02.135Z" },
{ url = "https://files.pythonhosted.org/packages/81/6a/35674445b1d38161148558a3ff51b0aa7f0b54b1def3abe3fbd34efe05bc/coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b", size = 247433, upload-time = "2025-08-29T15:35:03.777Z" },
{ url = "https://files.pythonhosted.org/packages/18/27/98c99e7cafb288730a93535092eb433b5503d529869791681c4f2e2012a8/coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862", size = 245315, upload-time = "2025-08-29T15:35:05.629Z" },
{ url = "https://files.pythonhosted.org/packages/09/05/123e0dba812408c719c319dea05782433246f7aa7b67e60402d90e847545/coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2", size = 243385, upload-time = "2025-08-29T15:35:07.494Z" },
{ url = "https://files.pythonhosted.org/packages/67/52/d57a42502aef05c6325f28e2e81216c2d9b489040132c18725b7a04d1448/coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78", size = 244343, upload-time = "2025-08-29T15:35:09.55Z" },
{ url = "https://files.pythonhosted.org/packages/6b/22/7f6fad7dbb37cf99b542c5e157d463bd96b797078b1ec506691bc836f476/coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c", size = 219530, upload-time = "2025-08-29T15:35:11.167Z" },
{ url = "https://files.pythonhosted.org/packages/62/30/e2fda29bfe335026027e11e6a5e57a764c9df13127b5cf42af4c3e99b937/coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf", size = 220432, upload-time = "2025-08-29T15:35:12.902Z" },
{ url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" },
]
[package.optional-dependencies]
toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]]
name = "dirty-equals"
source = { editable = "." }
[package.optional-dependencies]
pydantic = [
{ name = "pydantic" },
]
[package.dev-dependencies]
dev = [
{ name = "coverage", extra = ["toml"] },
{ name = "mypy" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "pytest" },
{ name = "pytest-examples" },
{ name = "pytest-pretty" },
{ name = "ruff" },
{ name = "types-pytz" },
]
docs = [
{ name = "mike" },
{ name = "mkdocs" },
{ name = "mkdocs-autorefs" },
{ name = "mkdocs-material" },
{ name = "mkdocs-simple-hooks" },
{ name = "mkdocstrings", extra = ["python"] },
]
[package.metadata]
requires-dist = [{ name = "pydantic", marker = "extra == 'pydantic'", specifier = ">=2.4.2" }]
provides-extras = ["pydantic"]
[package.metadata.requires-dev]
dev = [
{ name = "coverage", extras = ["toml"], specifier = ">=7.6.1" },
{ name = "mypy", specifier = ">=1.14.1" },
{ name = "packaging", specifier = ">=25.0" },
{ name = "pydantic", specifier = ">=2.10.6" },
{ name = "pytest", specifier = ">=8.3.5" },
{ name = "pytest-examples", specifier = ">=0.0.18" },
{ name = "pytest-pretty", specifier = ">=1.2.0" },
{ name = "ruff", specifier = ">=0.13.1" },
{ name = "types-pytz", specifier = ">=2024.2.0.20241221" },
]
docs = [
{ name = "mike", specifier = "==2.1.3" },
{ name = "mkdocs", specifier = "==1.6.0" },
{ name = "mkdocs-autorefs", specifier = "==1.0.1" },
{ name = "mkdocs-material", specifier = "==9.5.31" },
{ name = "mkdocs-simple-hooks", specifier = "==0.1.5" },
{ name = "mkdocstrings", extras = ["python"], specifier = "==0.25.2" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
[[package]]
name = "ghp-import"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
]
[[package]]
name = "griffe"
version = "1.14.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "importlib-metadata"
version = "8.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
]
[[package]]
name = "importlib-resources"
version = "6.5.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "markdown"
version = "3.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "mdurl", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "mdurl", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" },
{ url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" },
{ url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" },
{ url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" },
{ url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" },
{ url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" },
{ url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" },
{ url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" },
{ url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" },
{ url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" },
{ url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" },
{ url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" },
{ url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" },
{ url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" },
{ url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" },
{ url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" },
{ url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" },
{ url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" },
{ url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" },
{ url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" },
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
{ url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" },
{ url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" },
{ url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" },
{ url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" },
{ url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" },
{ url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" },
{ url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" },
{ url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" },
{ url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" },
{ url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "mergedeep"
version = "1.3.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" },
]
[[package]]
name = "mike"
version = "2.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata" },
{ name = "importlib-resources" },
{ name = "jinja2" },
{ name = "mkdocs" },
{ name = "pyparsing" },
{ name = "pyyaml" },
{ name = "pyyaml-env-tag" },
{ name = "verspec" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ab/f7/2933f1a1fb0e0f077d5d6a92c6c7f8a54e6128241f116dff4df8b6050bbf/mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810", size = 38119, upload-time = "2024-08-13T05:02:14.167Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a", size = 33754, upload-time = "2024-08-13T05:02:12.515Z" },
]
[[package]]
name = "mkdocs"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "ghp-import" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mergedeep" },
{ name = "mkdocs-get-deps" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "pyyaml" },
{ name = "pyyaml-env-tag" },
{ name = "watchdog" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cc/6b/26b33cc8ad54e8bc0345cddc061c2c5c23e364de0ecd97969df23f95a673/mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512", size = 3888392, upload-time = "2024-04-20T17:55:45.205Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/c0/930dcf5a3e96b9c8e7ad15502603fc61d495479699e2d2c381e3d37294d1/mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7", size = 3862264, upload-time = "2024-04-20T17:55:42.126Z" },
]
[[package]]
name = "mkdocs-autorefs"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/75/0ced93354fd9df40531c548f07d6462912eea9519e8cd78a8e6b42d73c4a/mkdocs_autorefs-1.0.1.tar.gz", hash = "sha256:f684edf847eced40b570b57846b15f0bf57fb93ac2c510450775dcf16accb971", size = 17743, upload-time = "2024-02-29T15:52:19.753Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f6/01/d413c98335ed75d8c211afb91320811366d55fb0ef7f4b01b9ab19630eac/mkdocs_autorefs-1.0.1-py3-none-any.whl", hash = "sha256:aacdfae1ab197780fb7a2dac92ad8a3d8f7ca8049a9cbe56a4218cd52e8da570", size = 13444, upload-time = "2024-02-29T15:51:52.989Z" },
]
[[package]]
name = "mkdocs-get-deps"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "mergedeep" },
{ name = "platformdirs" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
]
[[package]]
name = "mkdocs-material"
version = "9.5.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "babel" },
{ name = "colorama" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "mkdocs" },
{ name = "mkdocs-material-extensions" },
{ name = "paginate" },
{ name = "pygments" },
{ name = "pymdown-extensions" },
{ name = "regex" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/6b/ebc5dfe25614aabd3cfbd7bd18c4224194dfce0eb01d221fb3bf8988b380/mkdocs_material-9.5.31.tar.gz", hash = "sha256:31833ec664772669f5856f4f276bf3fdf0e642a445e64491eda459249c3a1ca8", size = 4103722, upload-time = "2024-08-02T09:29:19.188Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/ae/2f3aaab2abfe76e5e3073cf9429895449c30168d04421cf73cbe48b4e11b/mkdocs_material-9.5.31-py3-none-any.whl", hash = "sha256:1b1f49066fdb3824c1e96d6bacd2d4375de4ac74580b47e79ff44c4d835c5fcb", size = 8818355, upload-time = "2024-08-02T09:29:13.889Z" },
]
[[package]]
name = "mkdocs-material-extensions"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
]
[[package]]
name = "mkdocs-simple-hooks"
version = "0.1.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/93/565f98d6810e3b493e61160aea1cceb8653331576e7fa7f048ac7e7cdf62/mkdocs-simple-hooks-0.1.5.tar.gz", hash = "sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c", size = 4037, upload-time = "2022-01-07T09:11:18.345Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/e9/7bf0f928f5b6cdd602d4a01d52e5fc1eba8d3ba6d97619a88fc271a625f8/mkdocs_simple_hooks-0.1.5-py3-none-any.whl", hash = "sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a", size = 4596, upload-time = "2022-01-07T09:11:17.022Z" },
]
[[package]]
name = "mkdocstrings"
version = "0.25.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mkdocs" },
{ name = "mkdocs-autorefs" },
{ name = "platformdirs" },
{ name = "pymdown-extensions" },
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/24/a6/d544fae9749b19e23fb590f6344f9eae3a312323065070b4874236bb0e04/mkdocstrings-0.25.2.tar.gz", hash = "sha256:5cf57ad7f61e8be3111a2458b4e49c2029c9cb35525393b179f9c916ca8042dc", size = 91796, upload-time = "2024-07-25T14:55:49.895Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/86/ee2aef075cc9a62a4f087c3c3f4e3e8a8318afe05a92f8f8415f1bf1af64/mkdocstrings-0.25.2-py3-none-any.whl", hash = "sha256:9e2cda5e2e12db8bb98d21e3410f3f27f8faab685a24b03b06ba7daa5b92abfc", size = 29289, upload-time = "2024-07-25T14:55:47.275Z" },
]
[package.optional-dependencies]
python = [
{ name = "mkdocstrings-python" },
]
[[package]]
name = "mkdocstrings-python"
version = "1.10.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "griffe" },
{ name = "mkdocs-autorefs" },
{ name = "mkdocstrings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d8/df/c0c09bf79f1329da2422b5d1d2d4d84070aee7cf7f99df98d7b78be066a9/mkdocstrings_python-1.10.9.tar.gz", hash = "sha256:f344aaa47e727d8a2dc911e063025e58e2b7fb31a41110ccc3902aa6be7ca196", size = 162070, upload-time = "2024-08-30T18:05:04.607Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/45/35c2ade06b6dfd383795008260405a47e10aba74e2c9594741a1a1f034b0/mkdocstrings_python-1.10.9-py3-none-any.whl", hash = "sha256:cbe98710a6757dfd4dff79bf36cb9731908fb4c69dd2736b15270ae7a488243d", size = 108360, upload-time = "2024-08-30T18:05:02.608Z" },
]
[[package]]
name = "mypy"
version = "1.18.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" },
{ url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" },
{ url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" },
{ url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" },
{ url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" },
{ url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" },
{ url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" },
{ url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" },
{ url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" },
{ url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" },
{ url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" },
{ url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" },
{ url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" },
{ url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" },
{ url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" },
{ url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" },
{ url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" },
{ url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" },
{ url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" },
{ url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" },
{ url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" },
{ url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" },
{ url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" },
{ url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" },
{ url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" },
{ url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" },
{ url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" },
{ url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" },
{ url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" },
{ url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" },
{ url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "paginate"
version = "0.5.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "platformdirs"
version = "4.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
]
[[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 = "pydantic"
version = "2.11.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" },
{ url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" },
{ url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" },
{ url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" },
{ url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" },
{ url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" },
{ url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" },
{ url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" },
{ url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" },
{ url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" },
{ url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" },
{ url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" },
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" },
{ url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" },
{ url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" },
{ url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" },
{ url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" },
{ url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" },
{ url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" },
{ url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" },
{ url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" },
{ url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" },
{ url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" },
{ url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" },
{ url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" },
{ url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" },
{ url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" },
{ url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" },
{ url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" },
{ url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" },
{ url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" },
{ url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" },
{ url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" },
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
{ url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" },
{ url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" },
{ url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" },
{ url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" },
{ url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" },
{ url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" },
{ url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" },
{ url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" },
{ url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pymdown-extensions"
version = "10.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" },
]
[[package]]
name = "pyparsing"
version = "3.2.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" },
]
[[package]]
name = "pytest"
version = "8.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
]
[[package]]
name = "pytest-examples"
version = "0.0.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "black" },
{ name = "pytest" },
{ name = "ruff" },
]
sdist = { url = "https://files.pythonhosted.org/packages/af/71/4ae972fd95f474454aa450108ee1037830e7ba11840363e981b8d48fd16a/pytest_examples-0.0.18.tar.gz", hash = "sha256:9a464f007f805b113677a15e2f8942ebb92d7d3eb5312e9a405d018478ec9801", size = 21237, upload-time = "2025-05-06T07:46:10.705Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/09/52/7bbfb6e987d9a8a945f22941a8da63e3529465f1b106ef0e26f5df7c780d/pytest_examples-0.0.18-py3-none-any.whl", hash = "sha256:86c195b98c4e55049a0df3a0a990ca89123b7280473ab57608eecc6c47bcfe9c", size = 18169, upload-time = "2025-05-06T07:46:09.349Z" },
]
[[package]]
name = "pytest-pretty"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/d7/c699e0be5401fe9ccad484562f0af9350b4e48c05acf39fb3dab1932128f/pytest_pretty-1.3.0.tar.gz", hash = "sha256:97e9921be40f003e40ae78db078d4a0c1ea42bf73418097b5077970c2cc43bf3", size = 219297, upload-time = "2025-06-04T12:54:37.322Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/85/2f97a1b65178b0f11c9c77c35417a4cc5b99a80db90dad4734a129844ea5/pytest_pretty-1.3.0-py3-none-any.whl", hash = "sha256:074b9d5783cef9571494543de07e768a4dda92a3e85118d6c7458c67297159b7", size = 5620, upload-time = "2025-06-04T12:54:36.229Z" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "pytokens"
version = "0.1.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/30/5f/e959a442435e24f6fb5a01aec6c657079ceaca1b3baf18561c3728d681da/pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044", size = 12171, upload-time = "2025-02-19T14:51:22.001Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/e5/63bed382f6a7a5ba70e7e132b8b7b8abbcf4888ffa6be4877698dcfbed7d/pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b", size = 12046, upload-time = "2025-02-19T14:51:18.694Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" },
{ url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" },
{ url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" },
{ url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" },
{ url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" },
{ url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" },
{ url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" },
{ url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" },
{ url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" },
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
{ url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" },
{ url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" },
{ url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" },
{ url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" },
{ url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" },
{ url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" },
{ url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" },
{ url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" },
{ url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" },
]
[[package]]
name = "pyyaml-env-tag"
version = "1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
]
[[package]]
name = "regex"
version = "2025.9.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/d8/7e06171db8e55f917c5b8e89319cea2d86982e3fc46b677f40358223dece/regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788", size = 484829, upload-time = "2025-09-19T00:35:05.215Z" },
{ url = "https://files.pythonhosted.org/packages/8d/70/bf91bb39e5bedf75ce730ffbaa82ca585584d13335306d637458946b8b9f/regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4", size = 288993, upload-time = "2025-09-19T00:35:08.154Z" },
{ url = "https://files.pythonhosted.org/packages/fe/89/69f79b28365eda2c46e64c39d617d5f65a2aa451a4c94de7d9b34c2dc80f/regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61", size = 286624, upload-time = "2025-09-19T00:35:09.717Z" },
{ url = "https://files.pythonhosted.org/packages/44/31/81e62955726c3a14fcc1049a80bc716765af6c055706869de5e880ddc783/regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251", size = 780473, upload-time = "2025-09-19T00:35:11.013Z" },
{ url = "https://files.pythonhosted.org/packages/fb/23/07072b7e191fbb6e213dc03b2f5b96f06d3c12d7deaded84679482926fc7/regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746", size = 849290, upload-time = "2025-09-19T00:35:12.348Z" },
{ url = "https://files.pythonhosted.org/packages/b3/f0/aec7f6a01f2a112210424d77c6401b9015675fb887ced7e18926df4ae51e/regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2", size = 897335, upload-time = "2025-09-19T00:35:14.058Z" },
{ url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0", size = 789946, upload-time = "2025-09-19T00:35:15.403Z" },
{ url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8", size = 780787, upload-time = "2025-09-19T00:35:17.061Z" },
{ url = "https://files.pythonhosted.org/packages/4f/92/c54cdb4aa41009632e69817a5aa452673507f07e341076735a2f6c46a37c/regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea", size = 773632, upload-time = "2025-09-19T00:35:18.57Z" },
{ url = "https://files.pythonhosted.org/packages/db/99/75c996dc6a2231a8652d7ad0bfbeaf8a8c77612d335580f520f3ec40e30b/regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8", size = 844104, upload-time = "2025-09-19T00:35:20.259Z" },
{ url = "https://files.pythonhosted.org/packages/1c/f7/25aba34cc130cb6844047dbfe9716c9b8f9629fee8b8bec331aa9241b97b/regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25", size = 834794, upload-time = "2025-09-19T00:35:22.002Z" },
{ url = "https://files.pythonhosted.org/packages/51/eb/64e671beafa0ae29712268421597596d781704973551312b2425831d4037/regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29", size = 778535, upload-time = "2025-09-19T00:35:23.298Z" },
{ url = "https://files.pythonhosted.org/packages/26/33/c0ebc0b07bd0bf88f716cca240546b26235a07710ea58e271cfe390ae273/regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444", size = 264115, upload-time = "2025-09-19T00:35:25.206Z" },
{ url = "https://files.pythonhosted.org/packages/59/39/aeb11a4ae68faaec2498512cadae09f2d8a91f1f65730fe62b9bffeea150/regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450", size = 276143, upload-time = "2025-09-19T00:35:26.785Z" },
{ url = "https://files.pythonhosted.org/packages/29/04/37f2d3fc334a1031fc2767c9d89cec13c2e72207c7e7f6feae8a47f4e149/regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442", size = 268473, upload-time = "2025-09-19T00:35:28.39Z" },
{ url = "https://files.pythonhosted.org/packages/58/61/80eda662fc4eb32bfedc331f42390974c9e89c7eac1b79cd9eea4d7c458c/regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a", size = 484832, upload-time = "2025-09-19T00:35:30.011Z" },
{ url = "https://files.pythonhosted.org/packages/a6/d9/33833d9abddf3f07ad48504ddb53fe3b22f353214bbb878a72eee1e3ddbf/regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8", size = 288994, upload-time = "2025-09-19T00:35:31.733Z" },
{ url = "https://files.pythonhosted.org/packages/2a/b3/526ee96b0d70ea81980cbc20c3496fa582f775a52e001e2743cc33b2fa75/regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414", size = 286619, upload-time = "2025-09-19T00:35:33.221Z" },
{ url = "https://files.pythonhosted.org/packages/65/4f/c2c096b02a351b33442aed5895cdd8bf87d372498d2100927c5a053d7ba3/regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a", size = 792454, upload-time = "2025-09-19T00:35:35.361Z" },
{ url = "https://files.pythonhosted.org/packages/24/15/b562c9d6e47c403c4b5deb744f8b4bf6e40684cf866c7b077960a925bdff/regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4", size = 858723, upload-time = "2025-09-19T00:35:36.949Z" },
{ url = "https://files.pythonhosted.org/packages/f2/01/dba305409849e85b8a1a681eac4c03ed327d8de37895ddf9dc137f59c140/regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a", size = 905899, upload-time = "2025-09-19T00:35:38.723Z" },
{ url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f", size = 798981, upload-time = "2025-09-19T00:35:40.416Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5e/72db90970887bbe02296612bd61b0fa31e6d88aa24f6a4853db3e96c575e/regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a", size = 781900, upload-time = "2025-09-19T00:35:42.077Z" },
{ url = "https://files.pythonhosted.org/packages/50/ff/596be45eea8e9bc31677fde243fa2904d00aad1b32c31bce26c3dbba0b9e/regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9", size = 852952, upload-time = "2025-09-19T00:35:43.751Z" },
{ url = "https://files.pythonhosted.org/packages/e5/1b/2dfa348fa551e900ed3f5f63f74185b6a08e8a76bc62bc9c106f4f92668b/regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2", size = 844355, upload-time = "2025-09-19T00:35:45.309Z" },
{ url = "https://files.pythonhosted.org/packages/f4/bf/aefb1def27fe33b8cbbb19c75c13aefccfbef1c6686f8e7f7095705969c7/regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95", size = 787254, upload-time = "2025-09-19T00:35:46.904Z" },
{ url = "https://files.pythonhosted.org/packages/e3/4e/8ef042e7cf0dbbb401e784e896acfc1b367b95dfbfc9ada94c2ed55a081f/regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07", size = 264129, upload-time = "2025-09-19T00:35:48.597Z" },
{ url = "https://files.pythonhosted.org/packages/b4/7d/c4fcabf80dcdd6821c0578ad9b451f8640b9110fb3dcb74793dd077069ff/regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9", size = 276160, upload-time = "2025-09-19T00:36:00.45Z" },
{ url = "https://files.pythonhosted.org/packages/64/f8/0e13c8ae4d6df9d128afaba138342d532283d53a4c1e7a8c93d6756c8f4a/regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df", size = 268471, upload-time = "2025-09-19T00:36:02.149Z" },
{ url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e", size = 486335, upload-time = "2025-09-19T00:36:03.661Z" },
{ url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a", size = 289720, upload-time = "2025-09-19T00:36:05.471Z" },
{ url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab", size = 287257, upload-time = "2025-09-19T00:36:07.072Z" },
{ url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5", size = 797463, upload-time = "2025-09-19T00:36:08.399Z" },
{ url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742", size = 862670, upload-time = "2025-09-19T00:36:10.101Z" },
{ url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425", size = 910881, upload-time = "2025-09-19T00:36:12.223Z" },
{ url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352", size = 802011, upload-time = "2025-09-19T00:36:13.901Z" },
{ url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d", size = 786668, upload-time = "2025-09-19T00:36:15.391Z" },
{ url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56", size = 856578, upload-time = "2025-09-19T00:36:16.845Z" },
{ url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e", size = 849017, upload-time = "2025-09-19T00:36:18.597Z" },
{ url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282", size = 788150, upload-time = "2025-09-19T00:36:20.464Z" },
{ url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459", size = 264536, upload-time = "2025-09-19T00:36:21.922Z" },
{ url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77", size = 275501, upload-time = "2025-09-19T00:36:23.4Z" },
{ url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5", size = 268601, upload-time = "2025-09-19T00:36:25.092Z" },
{ url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2", size = 485955, upload-time = "2025-09-19T00:36:26.822Z" },
{ url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb", size = 289583, upload-time = "2025-09-19T00:36:28.577Z" },
{ url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af", size = 287000, upload-time = "2025-09-19T00:36:30.161Z" },
{ url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29", size = 797535, upload-time = "2025-09-19T00:36:31.876Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f", size = 862603, upload-time = "2025-09-19T00:36:33.344Z" },
{ url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68", size = 910829, upload-time = "2025-09-19T00:36:34.826Z" },
{ url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783", size = 802059, upload-time = "2025-09-19T00:36:36.664Z" },
{ url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac", size = 786781, upload-time = "2025-09-19T00:36:38.168Z" },
{ url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e", size = 856578, upload-time = "2025-09-19T00:36:40.129Z" },
{ url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23", size = 849119, upload-time = "2025-09-19T00:36:41.651Z" },
{ url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f", size = 788219, upload-time = "2025-09-19T00:36:43.575Z" },
{ url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d", size = 264517, upload-time = "2025-09-19T00:36:45.503Z" },
{ url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d", size = 275481, upload-time = "2025-09-19T00:36:46.965Z" },
{ url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb", size = 268598, upload-time = "2025-09-19T00:36:48.314Z" },
{ url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2", size = 489765, upload-time = "2025-09-19T00:36:49.996Z" },
{ url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3", size = 291228, upload-time = "2025-09-19T00:36:51.654Z" },
{ url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12", size = 289270, upload-time = "2025-09-19T00:36:53.118Z" },
{ url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0", size = 806326, upload-time = "2025-09-19T00:36:54.631Z" },
{ url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6", size = 871556, upload-time = "2025-09-19T00:36:56.208Z" },
{ url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef", size = 913817, upload-time = "2025-09-19T00:36:57.807Z" },
{ url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a", size = 811055, upload-time = "2025-09-19T00:36:59.762Z" },
{ url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d", size = 794534, upload-time = "2025-09-19T00:37:01.405Z" },
{ url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368", size = 866684, upload-time = "2025-09-19T00:37:03.441Z" },
{ url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90", size = 853282, upload-time = "2025-09-19T00:37:04.985Z" },
{ url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7", size = 797830, upload-time = "2025-09-19T00:37:06.697Z" },
{ url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e", size = 267281, upload-time = "2025-09-19T00:37:08.568Z" },
{ url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730", size = 278724, upload-time = "2025-09-19T00:37:10.023Z" },
{ url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a", size = 269771, upload-time = "2025-09-19T00:37:13.041Z" },
{ url = "https://files.pythonhosted.org/packages/44/b7/3b4663aa3b4af16819f2ab6a78c4111c7e9b066725d8107753c2257448a5/regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129", size = 486130, upload-time = "2025-09-19T00:37:14.527Z" },
{ url = "https://files.pythonhosted.org/packages/80/5b/4533f5d7ac9c6a02a4725fe8883de2aebc713e67e842c04cf02626afb747/regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea", size = 289539, upload-time = "2025-09-19T00:37:16.356Z" },
{ url = "https://files.pythonhosted.org/packages/b8/8d/5ab6797c2750985f79e9995fad3254caa4520846580f266ae3b56d1cae58/regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1", size = 287233, upload-time = "2025-09-19T00:37:18.025Z" },
{ url = "https://files.pythonhosted.org/packages/cb/1e/95afcb02ba8d3a64e6ffeb801718ce73471ad6440c55d993f65a4a5e7a92/regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47", size = 797876, upload-time = "2025-09-19T00:37:19.609Z" },
{ url = "https://files.pythonhosted.org/packages/c8/fb/720b1f49cec1f3b5a9fea5b34cd22b88b5ebccc8c1b5de9cc6f65eed165a/regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379", size = 863385, upload-time = "2025-09-19T00:37:21.65Z" },
{ url = "https://files.pythonhosted.org/packages/a9/ca/e0d07ecf701e1616f015a720dc13b84c582024cbfbb3fc5394ae204adbd7/regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203", size = 910220, upload-time = "2025-09-19T00:37:23.723Z" },
{ url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164", size = 801827, upload-time = "2025-09-19T00:37:25.684Z" },
{ url = "https://files.pythonhosted.org/packages/b8/a6/740fbd9fcac31a1305a8eed30b44bf0f7f1e042342be0a4722c0365ecfca/regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb", size = 786843, upload-time = "2025-09-19T00:37:27.62Z" },
{ url = "https://files.pythonhosted.org/packages/80/a7/0579e8560682645906da640c9055506465d809cb0f5415d9976f417209a6/regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743", size = 857430, upload-time = "2025-09-19T00:37:29.362Z" },
{ url = "https://files.pythonhosted.org/packages/8d/9b/4dc96b6c17b38900cc9fee254fc9271d0dde044e82c78c0811b58754fde5/regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282", size = 848612, upload-time = "2025-09-19T00:37:31.42Z" },
{ url = "https://files.pythonhosted.org/packages/b3/6a/6f659f99bebb1775e5ac81a3fb837b85897c1a4ef5acffd0ff8ffe7e67fb/regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773", size = 787967, upload-time = "2025-09-19T00:37:34.019Z" },
{ url = "https://files.pythonhosted.org/packages/61/35/9e35665f097c07cf384a6b90a1ac11b0b1693084a0b7a675b06f760496c6/regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788", size = 269847, upload-time = "2025-09-19T00:37:35.759Z" },
{ url = "https://files.pythonhosted.org/packages/af/64/27594dbe0f1590b82de2821ebfe9a359b44dcb9b65524876cd12fabc447b/regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3", size = 278755, upload-time = "2025-09-19T00:37:37.367Z" },
{ url = "https://files.pythonhosted.org/packages/30/a3/0cd8d0d342886bd7d7f252d701b20ae1a3c72dc7f34ef4b2d17790280a09/regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d", size = 271873, upload-time = "2025-09-19T00:37:39.125Z" },
{ url = "https://files.pythonhosted.org/packages/99/cb/8a1ab05ecf404e18b54348e293d9b7a60ec2bd7aa59e637020c5eea852e8/regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306", size = 489773, upload-time = "2025-09-19T00:37:40.968Z" },
{ url = "https://files.pythonhosted.org/packages/93/3b/6543c9b7f7e734d2404fa2863d0d710c907bef99d4598760ed4563d634c3/regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946", size = 291221, upload-time = "2025-09-19T00:37:42.901Z" },
{ url = "https://files.pythonhosted.org/packages/cd/91/e9fdee6ad6bf708d98c5d17fded423dcb0661795a49cba1b4ffb8358377a/regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f", size = 289268, upload-time = "2025-09-19T00:37:44.823Z" },
{ url = "https://files.pythonhosted.org/packages/94/a6/bc3e8a918abe4741dadeaeb6c508e3a4ea847ff36030d820d89858f96a6c/regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95", size = 806659, upload-time = "2025-09-19T00:37:46.684Z" },
{ url = "https://files.pythonhosted.org/packages/2b/71/ea62dbeb55d9e6905c7b5a49f75615ea1373afcad95830047e4e310db979/regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b", size = 871701, upload-time = "2025-09-19T00:37:48.882Z" },
{ url = "https://files.pythonhosted.org/packages/6a/90/fbe9dedb7dad24a3a4399c0bae64bfa932ec8922a0a9acf7bc88db30b161/regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3", size = 913742, upload-time = "2025-09-19T00:37:51.015Z" },
{ url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571", size = 811117, upload-time = "2025-09-19T00:37:52.686Z" },
{ url = "https://files.pythonhosted.org/packages/2a/da/435f29fddfd015111523671e36d30af3342e8136a889159b05c1d9110480/regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad", size = 794647, upload-time = "2025-09-19T00:37:54.626Z" },
{ url = "https://files.pythonhosted.org/packages/23/66/df5e6dcca25c8bc57ce404eebc7342310a0d218db739d7882c9a2b5974a3/regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494", size = 866747, upload-time = "2025-09-19T00:37:56.367Z" },
{ url = "https://files.pythonhosted.org/packages/82/42/94392b39b531f2e469b2daa40acf454863733b674481fda17462a5ffadac/regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b", size = 853434, upload-time = "2025-09-19T00:37:58.39Z" },
{ url = "https://files.pythonhosted.org/packages/a8/f8/dcc64c7f7bbe58842a8f89622b50c58c3598fbbf4aad0a488d6df2c699f1/regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41", size = 798024, upload-time = "2025-09-19T00:38:00.397Z" },
{ url = "https://files.pythonhosted.org/packages/20/8d/edf1c5d5aa98f99a692313db813ec487732946784f8f93145e0153d910e5/regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096", size = 273029, upload-time = "2025-09-19T00:38:02.383Z" },
{ url = "https://files.pythonhosted.org/packages/a7/24/02d4e4f88466f17b145f7ea2b2c11af3a942db6222429c2c146accf16054/regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a", size = 282680, upload-time = "2025-09-19T00:38:04.102Z" },
{ url = "https://files.pythonhosted.org/packages/1f/a3/c64894858aaaa454caa7cc47e2f225b04d3ed08ad649eacf58d45817fad2/regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01", size = 273034, upload-time = "2025-09-19T00:38:05.807Z" },
{ url = "https://files.pythonhosted.org/packages/ed/d2/5b0ded10467d6e96f78de5e6f195b7f9b57251f411b1090004597cffe5d9/regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40", size = 484847, upload-time = "2025-09-19T00:38:07.367Z" },
{ url = "https://files.pythonhosted.org/packages/55/35/051da2c0ae6124e3f1aa1442ecc2bb4e2de930e95433bce1301a2e7ae255/regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638", size = 288995, upload-time = "2025-09-19T00:38:09.253Z" },
{ url = "https://files.pythonhosted.org/packages/22/4b/4bfc51cad95263d25b6ed8c5253831b2536e8e279e6736d0a08c9f7ffe98/regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12", size = 286642, upload-time = "2025-09-19T00:38:11.012Z" },
{ url = "https://files.pythonhosted.org/packages/0e/67/d2f3e2483e09d1e9f7d93b4fe106b04933fba5e619bc901530d1c90d62da/regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c", size = 779896, upload-time = "2025-09-19T00:38:12.732Z" },
{ url = "https://files.pythonhosted.org/packages/14/5e/49a4f07ce6f5563de02b0e321220b9534f3fd3bae275311b785dd618aea5/regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408", size = 848954, upload-time = "2025-09-19T00:38:14.716Z" },
{ url = "https://files.pythonhosted.org/packages/00/8d/f5995ae51225c77ca9215d78ceb1dc30c52fa2b22c41dac977214e8b4bbd/regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d", size = 896770, upload-time = "2025-09-19T00:38:16.381Z" },
{ url = "https://files.pythonhosted.org/packages/6b/15/2a3a744d73a557337c7561db2114bab10b4e9941c626c03169ea62f42c8f/regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446", size = 789484, upload-time = "2025-09-19T00:38:18.183Z" },
{ url = "https://files.pythonhosted.org/packages/d8/27/e425f3d17d32062a657b836d0c8a68f5e71a9e6295fa637159f265eaa609/regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9", size = 780150, upload-time = "2025-09-19T00:38:19.879Z" },
{ url = "https://files.pythonhosted.org/packages/62/28/79dfae89b6fd7901b82611ac1a96ec25deceb7e918e9c5eb3f96cf5ad654/regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36", size = 773160, upload-time = "2025-09-19T00:38:21.641Z" },
{ url = "https://files.pythonhosted.org/packages/0b/67/df83d6ae608f487448e9be7ac26211af2afa2b6e34465fde3e07d1f11290/regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4", size = 843555, upload-time = "2025-09-19T00:38:23.696Z" },
{ url = "https://files.pythonhosted.org/packages/32/67/c65f56f3edd3f213d3aa41e9b9b07cc2247721a23d34bcfb2947dc0f4685/regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0", size = 834169, upload-time = "2025-09-19T00:38:25.997Z" },
{ url = "https://files.pythonhosted.org/packages/95/90/7fca37435e3aa1a032c38fa1e171fdaf809c8dbf2717508e3f6a92c75446/regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25", size = 778024, upload-time = "2025-09-19T00:38:28.043Z" },
{ url = "https://files.pythonhosted.org/packages/8b/05/c2ee512cdf34d6be5ac5cf938a58c1b79a9d96cbad404bc4d70404212edb/regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478", size = 264151, upload-time = "2025-09-19T00:38:30.23Z" },
{ url = "https://files.pythonhosted.org/packages/f8/2f/8414fb46181b6108484f04d670ece196db6734cc4c683f41125043fd3280/regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd", size = 276232, upload-time = "2025-09-19T00:38:31.981Z" },
{ url = "https://files.pythonhosted.org/packages/61/63/f40931d477e1ed4b53105d506758a58cfec1b052c12972054930ec743ee5/regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35", size = 268505, upload-time = "2025-09-19T00:38:34.015Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "rich"
version = "14.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" },
]
[[package]]
name = "ruff"
version = "0.13.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" },
{ url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" },
{ url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" },
{ url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" },
{ url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" },
{ url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" },
{ url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" },
{ url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" },
{ url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" },
{ url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" },
{ url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" },
{ url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" },
{ url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" },
{ url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" },
{ url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" },
{ url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" },
{ url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" },
{ url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "types-pytz"
version = "2025.2.0.20250809"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/07/e2/c774f754de26848f53f05defff5bb21dd9375a059d1ba5b5ea943cf8206e/types_pytz-2025.2.0.20250809.tar.gz", hash = "sha256:222e32e6a29bb28871f8834e8785e3801f2dc4441c715cd2082b271eecbe21e5", size = 10876, upload-time = "2025-08-09T03:14:17.453Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/d0/91c24fe54e565f2344d7a6821e6c6bb099841ef09007ea6321a0bac0f808/types_pytz-2025.2.0.20250809-py3-none-any.whl", hash = "sha256:4f55ed1b43e925cf851a756fe1707e0f5deeb1976e15bf844bcaa025e8fbd0db", size = 10095, upload-time = "2025-08-09T03:14:16.674Z" },
]
[[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 = "typing-inspection"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
]
[[package]]
name = "urllib3"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
]
[[package]]
name = "verspec"
version = "0.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/44/8126f9f0c44319b2efc65feaad589cadef4d77ece200ae3c9133d58464d0/verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e", size = 27123, upload-time = "2020-11-30T02:24:09.646Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31", size = 19640, upload-time = "2020-11-30T02:24:08.387Z" },
]
[[package]]
name = "watchdog"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
{ url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
{ url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
{ url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
{ url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
{ url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
{ url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
{ url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
{ url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
{ url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" },
{ url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" },
{ url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" },
{ url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
{ url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
{ url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" },
{ url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" },
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]
[[package]]
name = "zipp"
version = "3.23.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
]