1
0
Fork 0

Compare commits

...

10 commits

Author SHA1 Message Date
113e982c1f
Releasing debian version 2.10.2-1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:09:27 +01:00
9865822c70
Merging upstream version 2.10.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:09:26 +01:00
a51a0b430d
Releasing debian version 2.10.1-1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:09:04 +01:00
fe74caa497
Updating source url in copyright.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:09:04 +01:00
c0dc1fee23
Merging upstream version 2.10.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:09:03 +01:00
309fec0231
Releasing debian version 2.10.0-1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:06:38 +01:00
912971d44b
Merging upstream version 2.10.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:06:30 +01:00
9304378357
Releasing debian version 2.9.0-1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:06:04 +01:00
e6be59280f
Merging upstream version 2.9.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:05:59 +01:00
f1720b9d27
Releasing debian version 2.8.2-1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 14:05:07 +01:00
48 changed files with 2910 additions and 540 deletions

View file

@ -8,60 +8,90 @@ on:
- '**'
pull_request: {}
env:
COLUMNS: 150
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: set up python
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: '3.10'
enable-cache: true
- name: install
run: |
pip install -r requirements/pyproject.txt && pip install -r requirements/linting.txt
- name: Install dependencies
run: uv sync --python 3.12 --group lint --all-extras
- uses: pre-commit/action@v3.0.1
with:
extra_args: --all-files --verbose
env:
SKIP: no-commit-to-branch
test:
name: test py${{ matrix.python-version }} on ${{ matrix.os }}
name: test py${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu, macos, windows]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
env:
UV_PYTHON: ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v4
- name: set up python
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
- run: |
pip install -r requirements/pyproject.txt && pip install -r requirements/testing.txt
- name: Install dependencies
run: uv sync --extra all
- run: pip freeze
- name: Make coverage directory
run: mkdir coverage
- run: make test
- run: uv run --frozen coverage run -m pytest
env:
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-with-deps
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
- run: coverage xml
- uses: codecov/codecov-action@v4
- name: store coverage files
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}
path: coverage
include-hidden-files: true
coverage:
runs-on: ubuntu-latest
needs: [test]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: get coverage files
uses: actions/download-artifact@v4
with:
merge-multiple: true
path: coverage
- run: uv run --frozen coverage combine coverage
- run: uv run --frozen coverage report --fail-under 85
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
check:
if: always()
needs: [lint, test]
needs: [lint, test, coverage]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
@ -70,7 +100,6 @@ jobs:
jobs: ${{ toJSON(needs) }}
release:
name: Release
needs: [check]
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
@ -82,21 +111,18 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: set up python
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: '3.10'
- name: install
run: pip install -U build
enable-cache: true
- name: check GITHUB_REF matches package version
uses: samuelcolvin/check-python-version@v4.1
with:
version_file_path: pydantic_extra_types/__init__.py
- name: build
run: python -m build
- run: uv build
- name: Upload package to PyPI
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true

6
.gitignore vendored
View file

@ -4,16 +4,15 @@ venv/
.venv/
env3*/
Pipfile
*.lock
*.py[cod]
*.egg-info/
.python-version
/build/
dist/
.cache/
.mypy_cache/
test.py
.coverage
.hypothesis
/htmlcov/
/site/
/site.zip
@ -24,5 +23,4 @@ _build/
/sandbox/
/.ghtopdep_cache/
/worktrees/
.ruff_cache/
.python-version
/.ruff_cache/

View file

@ -1,24 +1,33 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: no-commit-to-branch # prevent direct commits to the `main` branch
- id: check-yaml
args: ['--unsafe']
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: local
hooks:
- 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
- id: format
name: Format
entry: make
args: [format]
language: system
types: [python]
pass_filenames: false
- id: lint
name: Lint
entry: make
args: [lint]
types: [python]
language: system
pass_filenames: false
- id: Typecheck
name: Typecheck
entry: make
args: [typecheck]
types: [python]
language: system
pass_filenames: false

View file

@ -2,6 +2,65 @@
## Latest Changes
## 2.10.2
* Add back Python 3.8 support by @Viicos in https://github.com/pydantic/pydantic-extra-types/pull/249
* ⬆ Bump astral-sh/setup-uv from 4 to 5 by @dependabot in https://github.com/pydantic/pydantic-extra-types/pull/282
* Preserve months when using the Pendulum Duration type by @gareththackeray in https://github.com/pydantic/pydantic-extra-types/pull/283
* ✨ Add type checking support and improve type hints across the codebase by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/285
* 📝 Add additional installation information to README by @oakhan3 in https://github.com/pydantic/pydantic-extra-types/pull/233
## 2.10.1
* Allow build with python-ulid 3.0.0 by @sunpoet in https://github.com/pydantic/pydantic-extra-types/pull/225
* 🔨 added automatic syntax-upgrade hook ~ pyupgrade by @janas-adam in https://github.com/pydantic/pydantic-extra-types/pull/229
* :fire: Revert adding pyupgrade as a hook in pre-commit by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/230
* isolate url in Currency by @edasubert in https://github.com/pydantic/pydantic-extra-types/pull/235
* lower case currency is valid by @edasubert in https://github.com/pydantic/pydantic-extra-types/pull/236
* Update SemanticVersion by @viccie30 in https://github.com/pydantic/pydantic-extra-types/pull/237
* Epoch - unix timestamp by @commonism in https://github.com/pydantic/pydantic-extra-types/pull/240
* :recycle: Migrate Pydantic Extra Types to use uv by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/241
* ⬆ Bump astral-sh/setup-uv from 3 to 4 by @dependabot in https://github.com/pydantic/pydantic-extra-types/pull/245
* ⬆ Bump pre-commit/action from 3.0.0 to 3.0.1 by @dependabot in https://github.com/pydantic/pydantic-extra-types/pull/244
* 🔖 Release version 2.10.1 by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/246
* Fix check python version for release by @hramezani in https://github.com/pydantic/pydantic-extra-types/pull/247
## 2.10.0
### Types
* Add semantic version type by @jbkroner in https://github.com/pydantic/pydantic-extra-types/pull/199
* feat: add S3Path by @lucianosrp in https://github.com/pydantic/pydantic-extra-types/pull/206
### Refactor
* feature: Improve phone number validator by @mZbZ in https://github.com/pydantic/pydantic-extra-types/pull/202
* Feature: Add phone number validator by @mZbZ in https://github.com/pydantic/pydantic-extra-types/pull/203
* Domain name string type by @matter1-git in https://github.com/pydantic/pydantic-extra-types/pull/212
* Adjust test_json_schema() for Pydantic 2.9 by @musicinmybrain in https://github.com/pydantic/pydantic-extra-types/pull/215
* Allow python-ulid 3.0 by @musicinmybrain in https://github.com/pydantic/pydantic-extra-types/pull/222
### Dependencies
* ⬆ Bump the python-packages group with 5 updates by @dependabot in https://github.com/pydantic/pydantic-extra-types/pull/201
* ✨ deprecate `semver` in favor of `semantic_version` by @07pepa in https://github.com/pydantic/pydantic-extra-types/pull/209
* 🔖 Release version 2.10.0 by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/224
## 2.9.0
### Types
* Add Semantic version type. PR [#195](https://github.com/pydantic/pydantic-extra-types/pull/195) by [@nikstuckenbrock](https://github.com/nikstuckenbrock)
* Add timezone name validation. PR [#193](https://github.com/pydantic/pydantic-extra-types/pull/193) by [@07pepa](https://github.com/07pepa)
### Refactor
* Replace try-except block by if-else statement. PR [#192](https://github.com/pydantic/pydantic-extra-types/pull/192) by [@maxsos](https://github.com/maxsos)
### Dependencies
* ⬆ Bump the python-packages group with 4 updates. PR [#194](https://github.com/pydantic/pydantic-extra-types/pull/194) by @dependabot
## 2.8.2
* 🐛 Preserve timezone information when validating Pendulum DateTimes. [#189](https://github.com/pydantic/pydantic-extra-types/pull/189) by [@chrisguidry

View file

@ -1,70 +1,53 @@
.DEFAULT_GOAL := all
sources = pydantic_extra_types tests
.PHONY: install
install:
python -m pip install -U pip
pip install -r requirements/all.txt
pip install -e .
.PHONY: .uv # Check that uv is installed
.uv:
@uv --version || echo 'Please install uv: https://docs.astral.sh/uv/getting-started/installation/'
.PHONY: refresh-lockfiles
refresh-lockfiles:
@echo "Updating requirements/*.txt files using pip-compile"
find requirements/ -name '*.txt' ! -name 'all.txt' -type f -delete
pip-compile -q --no-emit-index-url --resolver backtracking -o requirements/linting.txt requirements/linting.in
pip-compile -q --no-emit-index-url --resolver backtracking -o requirements/testing.txt requirements/testing.in
pip-compile -q --no-emit-index-url --resolver backtracking --extra all -o requirements/pyproject.txt pyproject.toml
pip install --dry-run -r requirements/all.txt
.PHONY: install ## Install the package, dependencies, and pre-commit for local development
install: .uv
uv sync --frozen --group all --all-extras
uv pip install pre-commit
pre-commit install --install-hooks
.PHONY: format
.PHONY: rebuild-lockfiles ## Rebuild lockfiles from scratch, updating all dependencies
rebuild-lockfiles: .uv
uv lock --upgrade
.PHONY: format # Format the code
format:
ruff check --fix $(sources)
ruff format $(sources)
uv run ruff format
uv run ruff check --fix --fix-only
.PHONY: lint
.PHONY: lint # Lint the code
lint:
ruff check $(sources)
ruff format --check $(sources)
uv run ruff format --check
uv run ruff check
.PHONY: mypy
mypy:
mypy pydantic_extra_types
.PHONY: typecheck # Typecheck the code
typecheck:
uv run mypy pydantic_extra_types
.PHONY: test
test:
coverage run -m pytest --durations=10
uv run pytest
.PHONY: testcov
testcov: test
@echo "building coverage html"
@coverage html
.PHONY: test-all-python # Run tests on Python 3.9 to 3.13
test-all-python:
UV_PROJECT_ENVIRONMENT=.venv39 uv run --python 3.9 coverage run -p -m pytest
UV_PROJECT_ENVIRONMENT=.venv310 uv run --python 3.10 coverage run -p -m pytest
UV_PROJECT_ENVIRONMENT=.venv311 uv run --python 3.11 coverage run -p -m pytest
UV_PROJECT_ENVIRONMENT=.venv312 uv run --python 3.12 coverage run -p -m pytest
UV_PROJECT_ENVIRONMENT=.venv313 uv run --python 3.13 coverage run -p -m pytest
@uv run coverage combine
@uv run coverage report
.PHONY: testcov-compile
testcov-compile: build-trace test
@echo "building coverage html"
@coverage html
.PHONY: testcov # Run tests and collect coverage data
testcov:
uv run coverage run -m pytest
@uv run coverage report
@uv run coverage html
.PHONY: all
all: lint mypy testcov
.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
rm -rf dist
rm -rf coverage.xml
rm -rf .ruff_cache
.PHONY: pre-commit
pre-commit:
pre-commit run --all-files --show-diff-on-failure
all: format lint testcov

View file

@ -8,3 +8,17 @@
A place for pydantic types that probably shouldn't exist in the main pydantic lib.
See [pydantic/pydantic#5012](https://github.com/pydantic/pydantic/issues/5012) for more info.
## Installation
Install this library with the desired extras dependencies as listed in [project.optional-dependencies](./pyproject.toml).
For example, if pendulum support was desired:
```shell
# via uv
$ uv add "pydantic-extra-types[pendulum]"
# via pip
$ pip install -U "pydantic-extra-types[pendulum]"
```

36
debian/changelog vendored
View file

@ -1,3 +1,39 @@
pydantic-extra-types (2.10.2-1) sid; urgency=medium
* Uploading to sid.
* Merging upstream version 2.10.2.
-- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 18 Jan 2025 07:21:36 +0100
pydantic-extra-types (2.10.1-1) sid; urgency=medium
* Uploading to sid.
* Merging upstream version 2.10.1.
* Updating source url in copyright.
-- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 09 Dec 2024 05:31:20 +0100
pydantic-extra-types (2.10.0-1) sid; urgency=medium
* Uploading to sid.
* Merging upstream version 2.10.0.
-- Daniel Baumann <daniel.baumann@progress-linux.org> Tue, 22 Oct 2024 18:58:21 +0200
pydantic-extra-types (2.9.0-1) sid; urgency=medium
* Uploading to sid.
* Merging upstream version 2.9.0.
-- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 13 Jul 2024 13:08:27 +0200
pydantic-extra-types (2.8.2-1) sid; urgency=medium
* Uploading to sid.
* Merging upstream version 2.8.2.
-- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 17 Jun 2024 11:11:32 +0200
pydantic-extra-types (2.8.0-1) sid; urgency=medium
* Uploading to sid.

2
debian/copyright vendored
View file

@ -1,7 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: pydantic-extra-types
Upstream-Contact: https://github.com/pydantic/pydantic-extra-types/issues
Source: https://github.com/pydantic/pydantic-extra-types/releases
Source: https://github.com/pydantic/pydantic-extra-types/tags
Files: *
Copyright: 2023-2024 Samuel Colvin <s@muelcolvin.com> and other contributors

View file

@ -1 +1 @@
__version__ = '2.8.2'
__version__ = '2.10.2'

View file

@ -1,5 +1,4 @@
"""
Color definitions are used as per the CSS3
"""Color definitions are used as per the CSS3
[CSS Color Module Level 3](http://www.w3.org/TR/css3-color/#svg-color) specification.
A few colors have multiple names referring to the sames colors, eg. `grey` and `gray` or `aqua` and `cyan`.
@ -26,9 +25,7 @@ HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, flo
class RGBA:
"""
Internal use only as a representation of a color.
"""
"""Internal use only as a representation of a color."""
__slots__ = 'r', 'g', 'b', 'alpha', '_tuple'
@ -67,9 +64,7 @@ rads = 2 * math.pi
class Color(_repr.Representation):
"""
Represents a color.
"""
"""Represents a color."""
__slots__ = '_original', '_rgba'
@ -101,14 +96,11 @@ class Color(_repr.Representation):
return field_schema
def original(self) -> ColorType:
"""
Original value passed to `Color`.
"""
"""Original value passed to `Color`."""
return self._original
def as_named(self, *, fallback: bool = False) -> str:
"""
Returns the name of the color if it can be found in `COLORS_BY_VALUE` dictionary,
"""Returns the name of the color if it can be found in `COLORS_BY_VALUE` dictionary,
otherwise returns the hexadecimal representation of the color or raises `ValueError`.
Args:
@ -123,14 +115,15 @@ class Color(_repr.Representation):
"""
if self._rgba.alpha is not None:
return self.as_hex()
rgb = cast(Tuple[int, int, int], self.as_rgb_tuple())
try:
rgb = cast('tuple[int, int, int]', self.as_rgb_tuple())
if rgb in COLORS_BY_VALUE:
return COLORS_BY_VALUE[rgb]
except KeyError as e:
else:
if fallback:
return self.as_hex()
else:
raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e
raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()')
def as_hex(self, format: Literal['short', 'long'] = 'short') -> str:
"""Returns the hexadecimal representation of the color.
@ -151,9 +144,7 @@ class Color(_repr.Representation):
return f'#{as_hex}'
def as_rgb(self) -> str:
"""
Color as an `rgb(<r>, <g>, <b>)` or `rgba(<r>, <g>, <b>, <a>)` string.
"""
"""Color as an `rgb(<r>, <g>, <b>)` or `rgba(<r>, <g>, <b>, <a>)` string."""
if self._rgba.alpha is None:
return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})'
else:
@ -163,8 +154,7 @@ class Color(_repr.Representation):
)
def as_rgb_tuple(self, *, alpha: bool | None = None) -> ColorTuple:
"""
Returns the color as an RGB or RGBA tuple.
"""Returns the color as an RGB or RGBA tuple.
Args:
alpha: Whether to include the alpha channel. There are three options for this input:
@ -184,9 +174,7 @@ class Color(_repr.Representation):
return r, g, b, self._alpha_float()
def as_hsl(self) -> str:
"""
Color as an `hsl(<h>, <s>, <l>)` or `hsl(<h>, <s>, <l>, <a>)` string.
"""
"""Color as an `hsl(<h>, <s>, <l>)` or `hsl(<h>, <s>, <l>, <a>)` string."""
if self._rgba.alpha is None:
h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore
return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})'
@ -195,8 +183,7 @@ class Color(_repr.Representation):
return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})'
def as_hsl_tuple(self, *, alpha: bool | None = None) -> HslColorTuple:
"""
Returns the color as an HSL or HSLA tuple.
"""Returns the color as an HSL or HSLA tuple.
Args:
alpha: Whether to include the alpha channel.
@ -271,8 +258,7 @@ def parse_tuple(value: tuple[Any, ...]) -> RGBA:
def parse_str(value: str) -> RGBA:
"""
Parse a string representing a color to an RGBA tuple.
"""Parse a string representing a color to an RGBA tuple.
Possible formats for the input string include:
@ -293,11 +279,8 @@ def parse_str(value: str) -> RGBA:
ValueError: If the input string cannot be parsed to an RGBA tuple.
"""
value_lower = value.lower()
try:
if value_lower in COLORS_BY_NAME:
r, g, b = COLORS_BY_NAME[value_lower]
except KeyError:
pass
else:
return ints_to_rgba(r, g, b, None)
m = re.fullmatch(r_hex_short, value_lower)
@ -337,8 +320,7 @@ def ints_to_rgba(
b: int | str,
alpha: float | None = None,
) -> RGBA:
"""
Converts integer or string values for RGB color and an optional alpha value to an `RGBA` object.
"""Converts integer or string values for RGB color and an optional alpha value to an `RGBA` object.
Args:
r: An integer or string representing the red color value.
@ -358,8 +340,7 @@ def ints_to_rgba(
def parse_color_value(value: int | str, max_val: int = 255) -> float:
"""
Parse the color value provided and return a number between 0 and 1.
"""Parse the color value provided and return a number between 0 and 1.
Args:
value: An integer or string color value.
@ -389,8 +370,7 @@ def parse_color_value(value: int | str, max_val: int = 255) -> float:
def parse_float_alpha(value: None | str | float | int) -> float | None:
"""
Parse an alpha value checking it's a valid float in the range 0 to 1.
"""Parse an alpha value checking it's a valid float in the range 0 to 1.
Args:
value: The input value to parse.
@ -426,8 +406,7 @@ def parse_float_alpha(value: None | str | float | int) -> float | None:
def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: float | None = None) -> RGBA:
"""
Parse raw hue, saturation, lightness, and alpha values and convert to RGBA.
"""Parse raw hue, saturation, lightness, and alpha values and convert to RGBA.
Args:
h: The hue value.
@ -455,8 +434,7 @@ def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: float | None =
def float_to_255(c: float) -> int:
"""
Converts a float value between 0 and 1 (inclusive) to an integer between 0 and 255 (inclusive).
"""Converts a float value between 0 and 1 (inclusive) to an integer between 0 and 255 (inclusive).
Args:
c: The float value to be converted. Must be between 0 and 1 (inclusive).

View file

@ -1,11 +1,12 @@
"""
The `pydantic_extra_types.coordinate` module provides the [`Latitude`][pydantic_extra_types.coordinate.Latitude],
"""The `pydantic_extra_types.coordinate` module provides the [`Latitude`][pydantic_extra_types.coordinate.Latitude],
[`Longitude`][pydantic_extra_types.coordinate.Longitude], and
[`Coordinate`][pydantic_extra_types.coordinate.Coordinate] data types.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, ClassVar, Tuple, Type
from typing import Any, ClassVar, Tuple
from pydantic import GetCoreSchemaHandler
from pydantic._internal import _repr
@ -19,12 +20,14 @@ class Latitude(float):
from pydantic import BaseModel
from pydantic_extra_types.coordinate import Latitude
class Location(BaseModel):
latitude: Latitude
location = Location(latitude=41.40338)
print(location)
#> latitude=41.40338
# > latitude=41.40338
```
"""
@ -32,7 +35,7 @@ class Latitude(float):
max: ClassVar[float] = 90.00
@classmethod
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.float_schema(ge=cls.min, le=cls.max)
@ -44,12 +47,14 @@ class Longitude(float):
from pydantic_extra_types.coordinate import Longitude
class Location(BaseModel):
longitude: Longitude
location = Location(longitude=2.17403)
print(location)
#> longitude=2.17403
# > longitude=2.17403
```
"""
@ -57,7 +62,7 @@ class Longitude(float):
max: ClassVar[float] = 180.00
@classmethod
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.float_schema(ge=cls.min, le=cls.max)
@ -76,11 +81,13 @@ class Coordinate(_repr.Representation):
from pydantic_extra_types.coordinate import Coordinate
class Location(BaseModel):
coordinate: Coordinate
location = Location(coordinate=(41.40338, 2.17403))
#> coordinate=Coordinate(latitude=41.40338, longitude=2.17403)
# > coordinate=Coordinate(latitude=41.40338, longitude=2.17403)
```
"""
@ -90,7 +97,7 @@ class Coordinate(_repr.Representation):
longitude: Longitude
@classmethod
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
schema_chain = [
core_schema.no_info_wrap_validator_function(cls._parse_str, core_schema.str_schema()),
core_schema.no_info_wrap_validator_function(

View file

@ -1,6 +1,4 @@
"""
Country definitions that are based on the [ISO 3166](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes).
"""
"""Country definitions that are based on the [ISO 3166](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes)."""
from __future__ import annotations
@ -69,12 +67,14 @@ class CountryAlpha2(str):
from pydantic_extra_types.country import CountryAlpha2
class Product(BaseModel):
made_in: CountryAlpha2
product = Product(made_in='ES')
print(product)
#> made_in='ES'
# > made_in='ES'
```
"""
@ -126,12 +126,14 @@ class CountryAlpha3(str):
from pydantic_extra_types.country import CountryAlpha3
class Product(BaseModel):
made_in: CountryAlpha3
product = Product(made_in="USA")
product = Product(made_in='USA')
print(product)
#> made_in='USA'
# > made_in='USA'
```
"""
@ -184,12 +186,14 @@ class CountryNumericCode(str):
from pydantic_extra_types.country import CountryNumericCode
class Product(BaseModel):
made_in: CountryNumericCode
product = Product(made_in="840")
product = Product(made_in='840')
print(product)
#> made_in='840'
# > made_in='840'
```
"""
@ -241,12 +245,14 @@ class CountryShortName(str):
from pydantic_extra_types.country import CountryShortName
class Product(BaseModel):
made_in: CountryShortName
product = Product(made_in="United States")
product = Product(made_in='United States')
print(product)
#> made_in='United States'
# > made_in='United States'
```
"""

View file

@ -1,6 +1,4 @@
"""
Currency definitions that are based on the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217).
"""
"""Currency definitions that are based on the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217)."""
from __future__ import annotations
@ -41,9 +39,11 @@ class ISO4217(str):
from pydantic_extra_types.currency_code import ISO4217
class Currency(BaseModel):
alpha_3: ISO4217
currency = Currency(alpha_3='AED')
print(currency)
# > alpha_3='AED'
@ -55,8 +55,7 @@ class ISO4217(str):
@classmethod
def _validate(cls, currency_code: str, _: core_schema.ValidationInfo) -> str:
"""
Validate a ISO 4217 language code from the provided str value.
"""Validate a ISO 4217 language code from the provided str value.
Args:
currency_code: The str value to be validated.
@ -68,6 +67,7 @@ class ISO4217(str):
Raises:
PydanticCustomError: If the ISO 4217 currency code is not valid.
"""
currency_code = currency_code.upper()
if currency_code not in cls.allowed_currencies:
raise PydanticCustomError(
'ISO4217', 'Invalid ISO 4217 currency code. See https://en.wikipedia.org/wiki/ISO_4217'
@ -98,9 +98,11 @@ class Currency(str):
from pydantic_extra_types.currency_code import Currency
class currency(BaseModel):
alpha_3: Currency
cur = currency(alpha_3='AED')
print(cur)
# > alpha_3='AED'
@ -114,8 +116,7 @@ class Currency(str):
@classmethod
def _validate(cls, currency_symbol: str, _: core_schema.ValidationInfo) -> str:
"""
Validate a subset of the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) format.
"""Validate a subset of the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) format.
It excludes bonds testing codes and precious metals.
Args:
@ -128,27 +129,27 @@ class Currency(str):
Raises:
PydanticCustomError: If the ISO 4217 currency code is not valid or is bond, precious metal or testing code.
"""
currency_symbol = currency_symbol.upper()
if currency_symbol not in cls.allowed_currencies:
raise PydanticCustomError(
'InvalidCurrency',
'Invalid currency code.'
' See https://en.wikipedia.org/wiki/ISO_4217. '
' See https://en.wikipedia.org/wiki/ISO_4217 . '
'Bonds, testing and precious metals codes are not allowed.',
)
return currency_symbol
@classmethod
def __get_pydantic_core_schema__(cls, _: type[Any], __: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""
Return a Pydantic CoreSchema with the currency subset of the
"""Return a Pydantic CoreSchema with the currency subset of the
[ISO4217](https://en.wikipedia.org/wiki/ISO_4217) format.
It excludes bonds testing codes and precious metals.
Args:
Args:
_: The source type.
__: The handler to get the CoreSchema.
Returns:
Returns:
A Pydantic CoreSchema with the subset of the currency subset of the
[ISO4217](https://en.wikipedia.org/wiki/ISO_4217) format.
It excludes bonds testing codes and precious metals.
@ -162,8 +163,7 @@ class Currency(str):
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
"""
Return a Pydantic JSON Schema with subset of the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) format.
"""Return a Pydantic JSON Schema with subset of the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) format.
Excluding bonds testing codes and precious metals.
Args:

View file

@ -0,0 +1,58 @@
"""The `domain_str` module provides the `DomainStr` data type.
This class depends on the `pydantic` package and implements custom validation for domain string format.
"""
from __future__ import annotations
import re
from typing import Any
from pydantic import GetCoreSchemaHandler
from pydantic_core import PydanticCustomError, core_schema
class DomainStr(str):
"""A string subclass with custom validation for domain string format."""
@classmethod
def validate(cls, __input_value: Any, _: Any) -> str:
"""Validate a domain name from the provided value.
Args:
__input_value: The value to be validated.
_: The source type to be converted.
Returns:
str: The parsed domain name.
"""
return cls._validate(__input_value)
@classmethod
def _validate(cls, v: Any) -> DomainStr:
if not isinstance(v, str):
raise PydanticCustomError('domain_type', 'Value must be a string')
v = v.strip().lower()
if len(v) < 1 or len(v) > 253:
raise PydanticCustomError('domain_length', 'Domain must be between 1 and 253 characters')
pattern = r'^([a-z0-9-]+(\.[a-z0-9-]+)+)$'
if not re.match(pattern, v):
raise PydanticCustomError('domain_format', 'Invalid domain format')
return cls(v)
@classmethod
def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.with_info_before_validator_function(
cls.validate,
core_schema.str_schema(),
)
@classmethod
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler
) -> dict[str, Any]:
# Cast the return value to dict[str, Any]
return dict(handler(schema))

View file

@ -0,0 +1,97 @@
from __future__ import annotations
import datetime
from typing import Any, Callable
import pydantic_core.core_schema
from pydantic import GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import CoreSchema, core_schema
EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
class _Base(datetime.datetime):
TYPE: str = ''
SCHEMA: pydantic_core.core_schema.CoreSchema
@classmethod
def __get_pydantic_json_schema__(
cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
field_schema: dict[str, Any] = {}
field_schema.update(type=cls.TYPE, format='date-time')
return field_schema
@classmethod
def __get_pydantic_core_schema__(
cls, source: type[Any], handler: Callable[[Any], CoreSchema]
) -> core_schema.CoreSchema:
return core_schema.with_info_after_validator_function(
cls._validate,
cls.SCHEMA,
serialization=core_schema.wrap_serializer_function_ser_schema(cls._f, return_schema=cls.SCHEMA),
)
@classmethod
def _validate(cls, __input_value: Any, _: Any) -> datetime.datetime:
return EPOCH + datetime.timedelta(seconds=__input_value)
@classmethod
def _f(cls, value: Any, serializer: Callable[[Any], Any]) -> Any: # pragma: no cover
raise NotImplementedError(cls)
class Number(_Base):
"""epoch.Number parses unix timestamp as float and converts it to datetime.
```py
from pydantic import BaseModel
from pydantic_extra_types import epoch
class LogEntry(BaseModel):
timestamp: epoch.Number
logentry = LogEntry(timestamp=1.1)
print(logentry)
# > timestamp=datetime.datetime(1970, 1, 1, 0, 0, 1, 100000, tzinfo=datetime.timezone.utc)
```
"""
TYPE = 'number'
SCHEMA = core_schema.float_schema()
@classmethod
def _f(cls, value: Any, serializer: Callable[[float], float]) -> float:
ts = value.timestamp()
return serializer(ts)
class Integer(_Base):
"""epoch.Integer parses unix timestamp as integer and converts it to datetime.
```
```py
from pydantic import BaseModel
from pydantic_extra_types import epoch
class LogEntry(BaseModel):
timestamp: epoch.Integer
logentry = LogEntry(timestamp=1)
print(logentry)
#> timestamp=datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=datetime.timezone.utc)
```
"""
TYPE = 'integer'
SCHEMA = core_schema.int_schema()
@classmethod
def _f(cls, value: Any, serializer: Callable[[int], int]) -> int:
ts = value.timestamp()
return serializer(int(ts))

View file

@ -1,5 +1,4 @@
"""
The `pydantic_extra_types.isbn` module provides functionality to recieve and validate ISBN.
"""The `pydantic_extra_types.isbn` module provides functionality to recieve and validate ISBN.
ISBN (International Standard Book Number) is a numeric commercial book identifier which is intended to be unique. This module provides a ISBN type for Pydantic models.
"""
@ -58,16 +57,16 @@ class ISBN(str):
class Book(BaseModel):
isbn: ISBN
book = Book(isbn="8537809667")
book = Book(isbn='8537809667')
print(book)
#> isbn='9788537809662'
# > isbn='9788537809662'
```
"""
@classmethod
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""
Return a Pydantic CoreSchema with the ISBN validation.
"""Return a Pydantic CoreSchema with the ISBN validation.
Args:
source: The source type to be converted.
@ -84,8 +83,7 @@ class ISBN(str):
@classmethod
def _validate(cls, __input_value: str, _: Any) -> str:
"""
Validate a ISBN from the provided str value.
"""Validate a ISBN from the provided str value.
Args:
__input_value: The str value to be validated.
@ -111,7 +109,6 @@ class ISBN(str):
Raises:
PydanticCustomError: If the ISBN is not valid.
"""
isbn_length = len(value)
if isbn_length not in (10, 13):
@ -143,7 +140,6 @@ class ISBN(str):
Returns:
The converted ISBN or the original value if no conversion is necessary.
"""
if len(value) == 10:
base_isbn = f'978{value[:-1]}'
isbn13_digit = isbn13_digit_calc(base_isbn)

View file

@ -1,6 +1,4 @@
"""
Language definitions that are based on the [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3) & [ISO 639-5](https://en.wikipedia.org/wiki/ISO_639-5).
"""
"""Language definitions that are based on the [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3) & [ISO 639-5](https://en.wikipedia.org/wiki/ISO_639-5)."""
from __future__ import annotations
@ -22,8 +20,7 @@ except ModuleNotFoundError as e: # pragma: no cover
@dataclass
class LanguageInfo:
"""
LanguageInfo is a dataclass that contains the language information.
"""LanguageInfo is a dataclass that contains the language information.
Args:
alpha2: The language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format.
@ -38,8 +35,7 @@ class LanguageInfo:
@lru_cache
def _languages() -> list[LanguageInfo]:
"""
Return a list of LanguageInfo objects containing the language information.
"""Return a list of LanguageInfo objects containing the language information.
Returns:
A list of LanguageInfo objects containing the language information.
@ -56,25 +52,19 @@ def _languages() -> list[LanguageInfo]:
@lru_cache
def _index_by_alpha2() -> dict[str, LanguageInfo]:
"""
Return a dictionary with the language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format as the key and the LanguageInfo object as the value.
"""
"""Return a dictionary with the language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format as the key and the LanguageInfo object as the value."""
return {language.alpha2: language for language in _languages() if language.alpha2 is not None}
@lru_cache
def _index_by_alpha3() -> dict[str, LanguageInfo]:
"""
Return a dictionary with the language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format as the key and the LanguageInfo object as the value.
"""
"""Return a dictionary with the language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format as the key and the LanguageInfo object as the value."""
return {language.alpha3: language for language in _languages()}
@lru_cache
def _index_by_name() -> dict[str, LanguageInfo]:
"""
Return a dictionary with the language name as the key and the LanguageInfo object as the value.
"""
"""Return a dictionary with the language name as the key and the LanguageInfo object as the value."""
return {language.name: language for language in _languages()}
@ -87,20 +77,21 @@ class LanguageAlpha2(str):
from pydantic_extra_types.language_code import LanguageAlpha2
class Movie(BaseModel):
audio_lang: LanguageAlpha2
subtitles_lang: LanguageAlpha2
movie = Movie(audio_lang='de', subtitles_lang='fr')
print(movie)
#> audio_lang='de' subtitles_lang='fr'
# > audio_lang='de' subtitles_lang='fr'
```
"""
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> LanguageAlpha2:
"""
Validate a language code in the ISO 639-1 alpha-2 format from the provided str value.
"""Validate a language code in the ISO 639-1 alpha-2 format from the provided str value.
Args:
__input_value: The str value to be validated.
@ -117,8 +108,7 @@ class LanguageAlpha2(str):
def __get_pydantic_core_schema__(
cls, source: type[Any], handler: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
"""
Return a Pydantic CoreSchema with the language code in the ISO 639-1 alpha-2 format validation.
"""Return a Pydantic CoreSchema with the language code in the ISO 639-1 alpha-2 format validation.
Args:
source: The source type.
@ -136,8 +126,7 @@ class LanguageAlpha2(str):
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
"""
Return a Pydantic JSON Schema with the language code in the ISO 639-1 alpha-2 format validation.
"""Return a Pydantic JSON Schema with the language code in the ISO 639-1 alpha-2 format validation.
Args:
schema: The Pydantic CoreSchema.
@ -170,20 +159,21 @@ class LanguageName(str):
from pydantic_extra_types.language_code import LanguageName
class Movie(BaseModel):
audio_lang: LanguageName
subtitles_lang: LanguageName
movie = Movie(audio_lang='Dutch', subtitles_lang='Mandarin Chinese')
print(movie)
#> audio_lang='Dutch' subtitles_lang='Mandarin Chinese'
# > audio_lang='Dutch' subtitles_lang='Mandarin Chinese'
```
"""
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> LanguageName:
"""
Validate a language name from the provided str value.
"""Validate a language name from the provided str value.
Args:
__input_value: The str value to be validated.
@ -200,8 +190,7 @@ class LanguageName(str):
def __get_pydantic_core_schema__(
cls, source: type[Any], handler: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
"""
Return a Pydantic CoreSchema with the language name validation.
"""Return a Pydantic CoreSchema with the language name validation.
Args:
source: The source type.
@ -236,9 +225,11 @@ class ISO639_3(str):
from pydantic_extra_types.language_code import ISO639_3
class Language(BaseModel):
alpha_3: ISO639_3
lang = Language(alpha_3='ssr')
print(lang)
# > alpha_3='ssr'
@ -250,8 +241,7 @@ class ISO639_3(str):
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ISO639_3:
"""
Validate a ISO 639-3 language code from the provided str value.
"""Validate a ISO 639-3 language code from the provided str value.
Args:
__input_value: The str value to be validated.
@ -273,8 +263,7 @@ class ISO639_3(str):
def __get_pydantic_core_schema__(
cls, _: type[Any], __: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
"""
Return a Pydantic CoreSchema with the ISO 639-3 language code validation.
"""Return a Pydantic CoreSchema with the ISO 639-3 language code validation.
Args:
_: The source type.
@ -293,8 +282,7 @@ class ISO639_3(str):
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
"""
Return a Pydantic JSON Schema with the ISO 639-3 language code validation.
"""Return a Pydantic JSON Schema with the ISO 639-3 language code validation.
Args:
schema: The Pydantic CoreSchema.
@ -318,9 +306,11 @@ class ISO639_5(str):
from pydantic_extra_types.language_code import ISO639_5
class Language(BaseModel):
alpha_3: ISO639_5
lang = Language(alpha_3='gem')
print(lang)
# > alpha_3='gem'
@ -333,8 +323,7 @@ class ISO639_5(str):
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ISO639_5:
"""
Validate a ISO 639-5 language code from the provided str value.
"""Validate a ISO 639-5 language code from the provided str value.
Args:
__input_value: The str value to be validated.
@ -356,8 +345,7 @@ class ISO639_5(str):
def __get_pydantic_core_schema__(
cls, _: type[Any], __: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
"""
Return a Pydantic CoreSchema with the ISO 639-5 language code validation.
"""Return a Pydantic CoreSchema with the ISO 639-5 language code validation.
Args:
_: The source type.
@ -376,8 +364,7 @@ class ISO639_5(str):
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
"""
Return a Pydantic JSON Schema with the ISO 639-5 language code validation.
"""Return a Pydantic JSON Schema with the ISO 639-5 language code validation.
Args:
schema: The Pydantic CoreSchema.

View file

@ -1,5 +1,4 @@
"""
The MAC address module provides functionality to parse and validate MAC addresses in different
"""The MAC address module provides functionality to parse and validate MAC addresses in different
formats, such as IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet format.
"""
@ -24,16 +23,15 @@ class MacAddress(str):
mac_address: MacAddress
network = Network(mac_address="00:00:5e:00:53:01")
network = Network(mac_address='00:00:5e:00:53:01')
print(network)
#> mac_address='00:00:5e:00:53:01'
# > mac_address='00:00:5e:00:53:01'
```
"""
@classmethod
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""
Return a Pydantic CoreSchema with the MAC address validation.
"""Return a Pydantic CoreSchema with the MAC address validation.
Args:
source: The source type to be converted.
@ -50,8 +48,7 @@ class MacAddress(str):
@classmethod
def _validate(cls, __input_value: str, _: Any) -> str:
"""
Validate a MAC Address from the provided str value.
"""Validate a MAC Address from the provided str value.
Args:
__input_value: The str value to be validated.
@ -65,9 +62,7 @@ class MacAddress(str):
@staticmethod
def validate_mac_address(value: bytes) -> str:
"""
Validate a MAC Address from the provided byte value.
"""
"""Validate a MAC Address from the provided byte value."""
if len(value) < 14:
raise PydanticCustomError(
'mac_address_len',

View file

@ -1,5 +1,4 @@
"""
The `pydantic_extra_types.payment` module provides the
"""The `pydantic_extra_types.payment` module provides the
[`PaymentCardNumber`][pydantic_extra_types.payment.PaymentCardNumber] data type.
"""

View file

@ -1,8 +1,9 @@
"""
Native Pendulum DateTime object implementation. This is a copy of the Pendulum DateTime object, but with a Pydantic
"""Native Pendulum DateTime object implementation. This is a copy of the Pendulum DateTime object, but with a Pydantic
CoreSchema implementation. This allows Pydantic to validate the DateTime object.
"""
from __future__ import annotations
try:
from pendulum import Date as _Date
from pendulum import DateTime as _DateTime
@ -13,7 +14,7 @@ except ModuleNotFoundError as e: # pragma: no cover
'The `pendulum_dt` module requires "pendulum" to be installed. You can install it with "pip install pendulum".'
) from e
from datetime import date, datetime, timedelta
from typing import Any, List, Type
from typing import Any
from pydantic import GetCoreSchemaHandler
from pydantic_core import PydanticCustomError, core_schema
@ -30,29 +31,29 @@ class DateTimeSettings(type):
class DateTime(_DateTime, metaclass=DateTimeSettings):
"""
A `pendulum.DateTime` object. At runtime, this type decomposes into pendulum.DateTime automatically.
"""A `pendulum.DateTime` object. At runtime, this type decomposes into pendulum.DateTime automatically.
This type exists because Pydantic throws a fit on unknown types.
```python
from pydantic import BaseModel
from pydantic_extra_types.pendulum_dt import DateTime
class test_model(BaseModel):
dt: DateTime
print(test_model(dt='2021-01-01T00:00:00+00:00'))
#> test_model(dt=DateTime(2021, 1, 1, 0, 0, 0, tzinfo=FixedTimezone(0, name="+00:00")))
# > test_model(dt=DateTime(2021, 1, 1, 0, 0, 0, tzinfo=FixedTimezone(0, name="+00:00")))
```
"""
__slots__: List[str] = []
__slots__: list[str] = []
@classmethod
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""
Return a Pydantic CoreSchema with the Datetime validation
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""Return a Pydantic CoreSchema with the Datetime validation
Args:
source: The source type to be converted.
@ -64,9 +65,8 @@ class DateTime(_DateTime, metaclass=DateTimeSettings):
return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.datetime_schema())
@classmethod
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> 'DateTime':
"""
Validate the datetime object and return it.
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> DateTime:
"""Validate the datetime object and return it.
Args:
value: The value to validate.
@ -96,29 +96,29 @@ class DateTime(_DateTime, metaclass=DateTimeSettings):
class Date(_Date):
"""
A `pendulum.Date` object. At runtime, this type decomposes into pendulum.Date automatically.
"""A `pendulum.Date` object. At runtime, this type decomposes into pendulum.Date automatically.
This type exists because Pydantic throws a fit on unknown types.
```python
from pydantic import BaseModel
from pydantic_extra_types.pendulum_dt import Date
class test_model(BaseModel):
dt: Date
print(test_model(dt='2021-01-01'))
#> test_model(dt=Date(2021, 1, 1))
# > test_model(dt=Date(2021, 1, 1))
```
"""
__slots__: List[str] = []
__slots__: list[str] = []
@classmethod
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""
Return a Pydantic CoreSchema with the Date validation
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""Return a Pydantic CoreSchema with the Date validation
Args:
source: The source type to be converted.
@ -130,9 +130,8 @@ class Date(_Date):
return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.date_schema())
@classmethod
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> 'Date':
"""
Validate the date object and return it.
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Date:
"""Validate the date object and return it.
Args:
value: The value to validate.
@ -156,29 +155,29 @@ class Date(_Date):
class Duration(_Duration):
"""
A `pendulum.Duration` object. At runtime, this type decomposes into pendulum.Duration automatically.
"""A `pendulum.Duration` object. At runtime, this type decomposes into pendulum.Duration automatically.
This type exists because Pydantic throws a fit on unknown types.
```python
from pydantic import BaseModel
from pydantic_extra_types.pendulum_dt import Duration
class test_model(BaseModel):
delta_t: Duration
print(test_model(delta_t='P1DT25H'))
#> test_model(delta_t=Duration(days=2, hours=1))
# > test_model(delta_t=Duration(days=2, hours=1))
```
"""
__slots__: List[str] = []
__slots__: list[str] = []
@classmethod
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""
Return a Pydantic CoreSchema with the Duration validation
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
"""Return a Pydantic CoreSchema with the Duration validation
Args:
source: The source type to be converted.
@ -190,9 +189,8 @@ class Duration(_Duration):
return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.timedelta_schema())
@classmethod
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> 'Duration':
"""
Validate the Duration object and return it.
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Duration:
"""Validate the Duration object and return it.
Args:
value: The value to validate.
@ -201,9 +199,25 @@ class Duration(_Duration):
Returns:
The validated value or raises a PydanticCustomError.
"""
# if we are passed an existing instance, pass it straight through.
if isinstance(value, (_Duration, timedelta)):
return Duration(seconds=value.total_seconds())
if isinstance(value, _Duration):
return Duration(
years=value.years,
months=value.months,
weeks=value.weeks,
days=value.remaining_days,
hours=value.hours,
minutes=value.minutes,
seconds=value.remaining_seconds,
microseconds=value.microseconds,
)
if isinstance(value, timedelta):
return Duration(
days=value.days,
seconds=value.seconds,
microseconds=value.microseconds,
)
try:
parsed = parse(value, exact=True)

View file

@ -1,5 +1,4 @@
"""
The `pydantic_extra_types.phone_numbers` module provides the
"""The `pydantic_extra_types.phone_numbers` module provides the
[`PhoneNumber`][pydantic_extra_types.phone_numbers.PhoneNumber] data type.
This class depends on the [phonenumbers] package, which is a Python port of Google's [libphonenumber].
@ -7,40 +6,36 @@ This class depends on the [phonenumbers] package, which is a Python port of Goog
from __future__ import annotations
from typing import Any, Callable, ClassVar, Generator
from collections.abc import Sequence
from dataclasses import dataclass
from functools import partial
from typing import Any, ClassVar
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import PydanticCustomError, core_schema
try:
import phonenumbers
from phonenumbers import PhoneNumber as BasePhoneNumber
from phonenumbers.phonenumberutil import NumberParseException
except ModuleNotFoundError as e: # pragma: no cover
raise RuntimeError(
'`PhoneNumber` requires "phonenumbers" to be installed. You can install it with "pip install phonenumbers"'
) from e
GeneratorCallableStr = Generator[Callable[..., str], None, None]
class PhoneNumber(str):
"""
A wrapper around [phonenumbers](https://pypi.org/project/phonenumbers/) package, which
"""A wrapper around [phonenumbers](https://pypi.org/project/phonenumbers/) package, which
is a Python port of Google's [libphonenumber](https://github.com/google/libphonenumber/).
"""
supported_regions: list[str] = sorted(phonenumbers.SUPPORTED_REGIONS)
"""The supported regions."""
supported_formats: list[str] = sorted([f for f in phonenumbers.PhoneNumberFormat.__dict__.keys() if f.isupper()])
"""The supported phone number formats."""
supported_regions: list[str] = []
"""The supported regions. If empty, all regions are supported."""
default_region_code: ClassVar[str | None] = None
"""The default region code to use when parsing phone numbers without an international prefix."""
phone_format: str = 'RFC3966'
"""The format of the phone number."""
min_length: int = 7
"""The minimum length of the phone number."""
max_length: int = 64
"""The maximum length of the phone number."""
@classmethod
def __get_pydantic_json_schema__(
@ -54,7 +49,7 @@ class PhoneNumber(str):
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.with_info_after_validator_function(
cls._validate,
core_schema.str_schema(min_length=cls.min_length, max_length=cls.max_length),
core_schema.str_schema(),
)
@classmethod
@ -66,6 +61,12 @@ class PhoneNumber(str):
if not phonenumbers.is_valid_number(parsed_number):
raise PydanticCustomError('value_error', 'value is not a valid phone number')
if cls.supported_regions and not any(
phonenumbers.is_valid_number_for_region(parsed_number, region_code=region)
for region in cls.supported_regions
):
raise PydanticCustomError('value_error', 'value is not from a supported region')
return phonenumbers.format_number(parsed_number, getattr(phonenumbers.PhoneNumberFormat, cls.phone_format))
def __eq__(self, other: Any) -> bool:
@ -73,3 +74,108 @@ class PhoneNumber(str):
def __hash__(self) -> int:
return super().__hash__()
@dataclass(frozen=True)
class PhoneNumberValidator:
"""A pydantic before validator for phone numbers using the [phonenumbers](https://pypi.org/project/phonenumbers/) package,
a Python port of Google's [libphonenumber](https://github.com/google/libphonenumber/).
Intended to be used to create custom pydantic data types using the `typing.Annotated` type construct.
Args:
default_region (str | None): The default region code to use when parsing phone numbers without an international prefix.
If `None` (default), the region must be supplied in the phone number as an international prefix.
number_format (str): The format of the phone number to return. See `phonenumbers.PhoneNumberFormat` for valid values.
supported_regions (list[str]): The supported regions. If empty, all regions are supported (default).
Returns:
The formatted phone number.
Example:
MyNumberType = Annotated[
Union[str, phonenumbers.PhoneNumber],
PhoneNumberValidator()
]
USNumberType = Annotated[
Union[str, phonenumbers.PhoneNumber],
PhoneNumberValidator(supported_regions=['US'], default_region='US')
]
class SomeModel(BaseModel):
phone_number: MyNumberType
us_number: USNumberType
"""
default_region: str | None = None
number_format: str = 'RFC3966'
supported_regions: Sequence[str] | None = None
def __post_init__(self) -> None:
if self.default_region and self.default_region not in phonenumbers.SUPPORTED_REGIONS:
raise ValueError(f'Invalid default region code: {self.default_region}')
if self.number_format not in (
number_format
for number_format in dir(phonenumbers.PhoneNumberFormat)
if not number_format.startswith('_') and number_format.isupper()
):
raise ValueError(f'Invalid number format: {self.number_format}')
if self.supported_regions:
for supported_region in self.supported_regions:
if supported_region not in phonenumbers.SUPPORTED_REGIONS:
raise ValueError(f'Invalid supported region code: {supported_region}')
@staticmethod
def _parse(
region: str | None,
number_format: str,
supported_regions: Sequence[str] | None,
phone_number: Any,
) -> str:
if not phone_number:
raise PydanticCustomError('value_error', 'value is not a valid phone number')
if not isinstance(phone_number, (str, BasePhoneNumber)):
raise PydanticCustomError('value_error', 'value is not a valid phone number')
parsed_number = None
if isinstance(phone_number, BasePhoneNumber):
parsed_number = phone_number
else:
try:
parsed_number = phonenumbers.parse(phone_number, region=region)
except NumberParseException as exc:
raise PydanticCustomError('value_error', 'value is not a valid phone number') from exc
if not phonenumbers.is_valid_number(parsed_number):
raise PydanticCustomError('value_error', 'value is not a valid phone number')
if supported_regions and not any(
phonenumbers.is_valid_number_for_region(parsed_number, region_code=region) for region in supported_regions
):
raise PydanticCustomError('value_error', 'value is not from a supported region')
return phonenumbers.format_number(parsed_number, getattr(phonenumbers.PhoneNumberFormat, number_format))
def __get_pydantic_core_schema__(self, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.no_info_before_validator_function(
partial(
self._parse,
self.default_region,
self.number_format,
self.supported_regions,
),
core_schema.str_schema(),
)
def __get_pydantic_json_schema__(
self, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
json_schema = handler(schema)
json_schema.update({'format': 'phone'})
return json_schema
def __hash__(self) -> int:
return super().__hash__()

View file

@ -1,9 +1,10 @@
"""
The `pydantic_extra_types.routing_number` module provides the
"""The `pydantic_extra_types.routing_number` module provides the
[`ABARoutingNumber`][pydantic_extra_types.routing_number.ABARoutingNumber] data type.
"""
from typing import Any, ClassVar, Type
from __future__ import annotations
from typing import Any, ClassVar
from pydantic import GetCoreSchemaHandler
from pydantic_core import PydanticCustomError, core_schema
@ -21,12 +22,14 @@ class ABARoutingNumber(str):
from pydantic_extra_types.routing_number import ABARoutingNumber
class BankAccount(BaseModel):
routing_number: ABARoutingNumber
account = BankAccount(routing_number='122105155')
print(account)
#> routing_number='122105155'
# > routing_number='122105155'
```
"""
@ -40,7 +43,7 @@ class ABARoutingNumber(str):
@classmethod
def __get_pydantic_core_schema__(
cls, source: Type[Any], handler: GetCoreSchemaHandler
cls, source: type[Any], handler: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
return core_schema.with_info_after_validator_function(
cls._validate,
@ -53,7 +56,7 @@ class ABARoutingNumber(str):
)
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> 'ABARoutingNumber':
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ABARoutingNumber:
return cls(__input_value)
@classmethod

View file

@ -0,0 +1,69 @@
"""The `pydantic_extra_types.s3` module provides the
[`S3Path`][pydantic_extra_types.s3.S3Path] data type.
A simpleAWS S3 URLs parser.
It also provides the `Bucket`, `Key` component.
"""
from __future__ import annotations
import re
from typing import Any, ClassVar
from pydantic import GetCoreSchemaHandler
from pydantic_core import core_schema
class S3Path(str):
"""An object representing a valid S3 path.
This type also allows you to access the `bucket` and `key` component of the S3 path.
It also contains the `last_key` which represents the last part of the path (tipically a file).
```python
from pydantic import BaseModel
from pydantic_extra_types.s3 import S3Path
class TestModel(BaseModel):
path: S3Path
p = 's3://my-data-bucket/2023/08/29/sales-report.csv'
model = TestModel(path=p)
model
# > TestModel(path=S3Path('s3://my-data-bucket/2023/08/29/sales-report.csv'))
model.path.bucket
# > 'my-data-bucket'
```
"""
patt: ClassVar[str] = r'^s3://([^/]+)/(.*?([^/]+)/?)$'
def __init__(self, value: str) -> None:
self.value = value
groups: tuple[str, str, str] = re.match(self.patt, self.value).groups() # type: ignore
self.bucket: str = groups[0]
self.key: str = groups[1]
self.last_key: str = groups[2]
def __str__(self) -> str: # pragma: no cover
return self.value
def __repr__(self) -> str: # pragma: no cover
return f'{self.__class__.__name__}({self.value!r})'
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> S3Path:
return cls(__input_value)
@classmethod
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
_, _ = source, handler
return core_schema.with_info_after_validator_function(
cls._validate,
core_schema.str_schema(pattern=cls.patt),
field_name=cls.__class__.__name__,
)

View file

@ -1,6 +1,4 @@
"""
script definitions that are based on the [ISO 15924](https://en.wikipedia.org/wiki/ISO_15924)
"""
"""script definitions that are based on the [ISO 15924](https://en.wikipedia.org/wiki/ISO_15924)"""
from __future__ import annotations
@ -27,9 +25,11 @@ class ISO_15924(str):
from pydantic_extra_types.language_code import ISO_15924
class Script(BaseModel):
alpha_4: ISO_15924
script = Script(alpha_4='Java')
print(lang)
# > script='Java'
@ -41,8 +41,7 @@ class ISO_15924(str):
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ISO_15924:
"""
Validate a ISO 15924 language code from the provided str value.
"""Validate a ISO 15924 language code from the provided str value.
Args:
__input_value: The str value to be validated.
@ -64,8 +63,7 @@ class ISO_15924(str):
def __get_pydantic_core_schema__(
cls, _: type[Any], __: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
"""
Return a Pydantic CoreSchema with the ISO 639-3 language code validation.
"""Return a Pydantic CoreSchema with the ISO 639-3 language code validation.
Args:
_: The source type.
@ -84,8 +82,7 @@ class ISO_15924(str):
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
"""
Return a Pydantic JSON Schema with the ISO 639-3 language code validation.
"""Return a Pydantic JSON Schema with the ISO 639-3 language code validation.
Args:
schema: The Pydantic CoreSchema.

View file

@ -0,0 +1,59 @@
"""SemanticVersion definition that is based on the Semantiv Versioning Specification [semver](https://semver.org/)."""
from typing import Any, Callable
from pydantic import GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
try:
import semver
except ModuleNotFoundError as e: # pragma: no cover
raise RuntimeError(
'The `semantic_version` module requires "semver" to be installed. You can install it with "pip install semver".'
) from e
class SemanticVersion(semver.Version):
"""Semantic version based on the official [semver thread](https://python-semver.readthedocs.io/en/latest/advanced/combine-pydantic-and-semver.html)."""
@classmethod
def __get_pydantic_core_schema__(
cls,
_source_type: Any,
_handler: Callable[[Any], core_schema.CoreSchema],
) -> core_schema.CoreSchema:
def validate_from_str(value: str) -> SemanticVersion:
return cls.parse(value)
from_str_schema = core_schema.chain_schema(
[
core_schema.str_schema(),
core_schema.no_info_plain_validator_function(validate_from_str),
]
)
return core_schema.json_or_python_schema(
json_schema=from_str_schema,
python_schema=core_schema.union_schema(
[
core_schema.is_instance_schema(semver.Version),
from_str_schema,
]
),
serialization=core_schema.to_string_ser_schema(),
)
@classmethod
def __get_pydantic_json_schema__(
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
return handler(
core_schema.str_schema(
pattern=r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
)
)
@classmethod
def validate_from_str(cls, value: str) -> 'SemanticVersion':
return cls.parse(value)

View file

@ -0,0 +1,77 @@
"""The _VersionPydanticAnnotation class provides functionality to parse and validate Semantic Versioning (SemVer) strings.
This class depends on the [semver](https://python-semver.readthedocs.io/en/latest/index.html) package.
"""
import warnings
from typing import Any, Callable
from pydantic import GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
from semver import Version
from typing_extensions import Annotated
warnings.warn(
'Use from pydantic_extra_types.semver import SemanticVersion instead. Will be removed in 3.0.0.', DeprecationWarning
)
class _VersionPydanticAnnotation(Version):
"""Represents a Semantic Versioning (SemVer).
Wraps the `version` type from `semver`.
Example:
```python
from pydantic import BaseModel
from pydantic_extra_types.semver import _VersionPydanticAnnotation
class appVersion(BaseModel):
version: _VersionPydanticAnnotation
app_version = appVersion(version='1.2.3')
print(app_version.version)
# > 1.2.3
```
"""
@classmethod
def __get_pydantic_core_schema__(
cls,
_source_type: Any,
_handler: Callable[[Any], core_schema.CoreSchema],
) -> core_schema.CoreSchema:
def validate_from_str(value: str) -> Version:
return Version.parse(value)
from_str_schema = core_schema.chain_schema(
[
core_schema.str_schema(),
core_schema.no_info_plain_validator_function(validate_from_str),
]
)
return core_schema.json_or_python_schema(
json_schema=from_str_schema,
python_schema=core_schema.union_schema(
[
core_schema.is_instance_schema(Version),
from_str_schema,
]
),
serialization=core_schema.to_string_ser_schema(),
)
@classmethod
def __get_pydantic_json_schema__(
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
return handler(core_schema.str_schema())
ManifestVersion = Annotated[Version, _VersionPydanticAnnotation]

View file

@ -0,0 +1,183 @@
"""Time zone name validation and serialization module."""
from __future__ import annotations
import importlib
import sys
import warnings
from typing import Any, Callable, cast
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import PydanticCustomError, core_schema
def _is_available(name: str) -> bool:
"""Check if a module is available for import."""
try:
importlib.import_module(name)
return True
except ModuleNotFoundError: # pragma: no cover
return False
def _tz_provider_from_zone_info() -> set[str]: # pragma: no cover
"""Get timezones from the zoneinfo module."""
from zoneinfo import available_timezones
return set(available_timezones())
def _tz_provider_from_pytz() -> set[str]: # pragma: no cover
"""Get timezones from the pytz module."""
from pytz import all_timezones
return set(all_timezones)
def _warn_about_pytz_usage() -> None:
"""Warn about using pytz with Python 3.9 or later."""
warnings.warn( # pragma: no cover
'Projects using Python 3.9 or later should be using the support now included as part of the standard library. '
'Please consider switching to the standard library (zoneinfo) module.'
)
def get_timezones() -> set[str]:
"""Determine the timezone provider and return available timezones."""
if _is_available('zoneinfo') and _is_available('tzdata'): # pragma: no cover
return _tz_provider_from_zone_info()
elif _is_available('pytz'): # pragma: no cover
return _tz_provider_from_pytz()
else: # pragma: no cover
if sys.version_info[:2] == (3, 8):
raise ImportError('No pytz module found. Please install it with "pip install pytz"')
raise ImportError('No timezone provider found. Please install tzdata with "pip install tzdata"')
class TimeZoneNameSettings(type):
def __new__(cls, name: str, bases: tuple[type, ...], dct: dict[str, Any], **kwargs: Any) -> type[TimeZoneName]:
dct['strict'] = kwargs.pop('strict', True)
return cast('type[TimeZoneName]', super().__new__(cls, name, bases, dct))
def __init__(cls, name: str, bases: tuple[type, ...], dct: dict[str, Any], **kwargs: Any) -> None:
super().__init__(name, bases, dct)
cls.strict = kwargs.get('strict', True)
def timezone_name_settings(**kwargs: Any) -> Callable[[type[TimeZoneName]], type[TimeZoneName]]:
def wrapper(cls: type[TimeZoneName]) -> type[TimeZoneName]:
cls.strict = kwargs.get('strict', True)
return cls
return wrapper
@timezone_name_settings(strict=True)
class TimeZoneName(str):
"""TimeZoneName is a custom string subclass for validating and serializing timezone names.
The TimeZoneName class uses the IANA Time Zone Database for validation.
It supports both strict and non-strict modes for timezone name validation.
## Examples:
Some examples of using the TimeZoneName class:
### Normal usage:
```python
from pydantic_extra_types.timezone_name import TimeZoneName
from pydantic import BaseModel
class Location(BaseModel):
city: str
timezone: TimeZoneName
loc = Location(city="New York", timezone="America/New_York")
print(loc.timezone)
>> America/New_York
```
### Non-strict mode:
```python
from pydantic_extra_types.timezone_name import TimeZoneName, timezone_name_settings
@timezone_name_settings(strict=False)
class TZNonStrict(TimeZoneName):
pass
tz = TZNonStrict("america/new_york")
print(tz)
>> america/new_york
```
"""
__slots__: list[str] = []
allowed_values: set[str] = set(get_timezones())
allowed_values_list: list[str] = sorted(allowed_values)
allowed_values_upper_to_correct: dict[str, str] = {val.upper(): val for val in allowed_values}
strict: bool
@classmethod
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> TimeZoneName:
"""Validate a time zone name from the provided str value.
Args:
__input_value: The str value to be validated.
_: The Pydantic ValidationInfo.
Returns:
The validated time zone name.
Raises:
PydanticCustomError: If the timezone name is not valid.
"""
if __input_value not in cls.allowed_values: # be fast for the most common case
if not cls.strict:
upper_value = __input_value.strip().upper()
if upper_value in cls.allowed_values_upper_to_correct:
return cls(cls.allowed_values_upper_to_correct[upper_value])
raise PydanticCustomError('TimeZoneName', 'Invalid timezone name.')
return cls(__input_value)
@classmethod
def __get_pydantic_core_schema__(
cls, _: type[Any], __: GetCoreSchemaHandler
) -> core_schema.AfterValidatorFunctionSchema:
"""Return a Pydantic CoreSchema with the timezone name validation.
Args:
_: The source type.
__: The handler to get the CoreSchema.
Returns:
A Pydantic CoreSchema with the timezone name validation.
"""
return core_schema.with_info_after_validator_function(
cls._validate,
core_schema.str_schema(min_length=1),
)
@classmethod
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> dict[str, Any]:
"""Return a Pydantic JSON Schema with the timezone name validation.
Args:
schema: The Pydantic CoreSchema.
handler: The handler to get the JSON Schema.
Returns:
A Pydantic JSON Schema with the timezone name validation.
"""
json_schema = handler(schema)
json_schema.update({'enum': cls.allowed_values_list})
return json_schema

View file

@ -1,5 +1,4 @@
"""
The `pydantic_extra_types.ULID` module provides the [`ULID`] data type.
"""The `pydantic_extra_types.ULID` module provides the [`ULID`] data type.
This class depends on the [python-ulid] package, which is a validate by the [ULID-spec](https://github.com/ulid/spec#implementations-in-other-languages).
"""
@ -25,8 +24,7 @@ UlidType = Union[str, bytes, int]
@dataclass
class ULID(_repr.Representation):
"""
A wrapper around [python-ulid](https://pypi.org/project/python-ulid/) package, which
"""A wrapper around [python-ulid](https://pypi.org/project/python-ulid/) package, which
is a validate by the [ULID-spec](https://github.com/ulid/spec#implementations-in-other-languages).
"""

View file

@ -9,8 +9,8 @@ path = 'pydantic_extra_types/__init__.py'
name = 'pydantic-extra-types'
description = 'Extra Pydantic types.'
authors = [
{name = 'Samuel Colvin', email = 's@muelcolvin.com'},
{name = 'Yasser Tahiri', email = 'hello@yezz.me'},
{ name = 'Samuel Colvin', email = 's@muelcolvin.com' },
{ name = 'Yasser Tahiri', email = 'hello@yezz.me' },
]
license = 'MIT'
readme = 'README.md'
@ -24,6 +24,7 @@ classifiers = [
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'Intended Audience :: System Administrators',
@ -38,27 +39,48 @@ classifiers = [
'Topic :: Internet',
]
requires-python = '>=3.8'
dependencies = [
'pydantic>=2.5.2',
]
dependencies = ['pydantic>=2.5.2','typing-extensions']
dynamic = ['version']
[project.optional-dependencies]
all = [
'phonenumbers>=8,<9',
'pycountry>=23',
'semver>=3.0.2',
'python-ulid>=1,<2; python_version<"3.9"',
'python-ulid>=1,<3; python_version>="3.9"',
'pendulum>=3.0.0,<4.0.0'
'python-ulid>=1,<4; python_version>="3.9"',
'pendulum>=3.0.0,<4.0.0',
'pytz>=2024.1',
'semver~=3.0.2',
'tzdata>=2024.1',
]
phonenumbers = ['phonenumbers>=8,<9']
pycountry = ['pycountry>=23']
semver = ['semver>=3.0.2']
python_ulid = [
'python-ulid>=1,<2; python_version<"3.9"',
'python-ulid>=1,<3; python_version>="3.9"',
'python-ulid>=1,<4; python_version>="3.9"',
]
pendulum = ['pendulum>=3.0.0,<4.0.0']
[dependency-groups]
dev = [
"coverage[toml]>=7.6.1",
"pytest-pretty>=1.2.0",
"dirty-equals>=0.7.1",
"pytest>=8.3.2",
]
lint = [
"ruff>=0.7.4",
"mypy>=0.910",
"annotated-types>=0.7.0",
"types-pytz>=2024.1.0.20240417",
]
extra = [
{ include-group = 'dev' },
{ include-group = 'lint' },
]
[project.urls]
Homepage = 'https://github.com/pydantic/pydantic-extra-types'
Source = 'https://github.com/pydantic/pydantic-extra-types'
@ -73,14 +95,21 @@ line-length = 120
target-version = 'py38'
[tool.ruff.lint]
extend-select = ['Q', 'RUF100', 'C90', 'UP', 'I']
flake8-quotes = {inline-quotes = 'single', multiline-quotes = 'double'}
isort = { known-first-party = ['pydantic_extra_types', 'tests'] }
extend-select = [
"Q",
"RUF100",
"C90",
"UP",
"I",
]
flake8-quotes = { inline-quotes = 'single', multiline-quotes = 'double' }
isort = {known-first-party = ['pydantic_extra_types', 'tests'] }
mccabe = { max-complexity = 14 }
pydocstyle = { convention = 'google' }
[tool.ruff.format]
quote-style = 'single'
docstring-code-format = true
quote-style = "single"
[tool.ruff.lint.per-file-ignores]
'pydantic_extra_types/color.py' = ['E741']
@ -109,7 +138,6 @@ exclude_lines = [
'@overload',
]
[tool.mypy]
strict = true
plugins = 'pydantic.mypy'
@ -120,7 +148,8 @@ filterwarnings = [
# This ignore will be removed when pycountry will drop py36 & support py311
'ignore:::pkg_resources',
# This ignore will be removed when pendulum fixes https://github.com/sdispater/pendulum/issues/834
'ignore:datetime.datetime.utcfromtimestamp.*:DeprecationWarning'
'ignore:datetime.datetime.utcfromtimestamp.*:DeprecationWarning',
' ignore:Use from pydantic_extra_types.semver import SemanticVersion instead. Will be removed in 3.0.0.:DeprecationWarning'
]
# configuring https://github.com/pydantic/hooky

View file

@ -1,3 +0,0 @@
-r ./pyproject.txt
-r ./linting.txt
-r ./testing.txt

View file

@ -1,4 +0,0 @@
pre-commit
mypy
annotated-types
ruff

View file

@ -1,37 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --no-emit-index-url --output-file=requirements/linting.txt requirements/linting.in
#
annotated-types==0.7.0
# via -r requirements/linting.in
cfgv==3.4.0
# via pre-commit
distlib==0.3.8
# via virtualenv
filelock==3.13.1
# via virtualenv
identify==2.5.35
# via pre-commit
mypy==1.10.0
# via -r requirements/linting.in
mypy-extensions==1.0.0
# via mypy
nodeenv==1.8.0
# via pre-commit
platformdirs==4.2.0
# via virtualenv
pre-commit==3.7.1
# via -r requirements/linting.in
pyyaml==6.0.1
# via pre-commit
ruff==0.4.7
# via -r requirements/linting.in
typing-extensions==4.10.0
# via mypy
virtualenv==20.25.1
# via pre-commit
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View file

@ -1,34 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --extra=all --no-emit-index-url --output-file=requirements/pyproject.txt pyproject.toml
#
annotated-types==0.7.0
# via pydantic
pendulum==3.0.0
# via pydantic-extra-types (pyproject.toml)
phonenumbers==8.13.31
# via pydantic-extra-types (pyproject.toml)
pycountry==23.12.11
# via pydantic-extra-types (pyproject.toml)
pydantic==2.6.3
# via pydantic-extra-types (pyproject.toml)
pydantic-core==2.16.3
# via pydantic
python-dateutil==2.8.2
# via
# pendulum
# time-machine
python-ulid==1.1.0
# via pydantic-extra-types (pyproject.toml)
six==1.16.0
# via python-dateutil
time-machine==2.13.0
# via pendulum
typing-extensions==4.10.0
# via
# pydantic
# pydantic-core
tzdata==2024.1
# via pendulum

View file

@ -1,6 +0,0 @@
dirty-equals
coverage[toml]
pytest
codecov
pytest-cov
pytest-pretty

View file

@ -1,50 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --no-emit-index-url --output-file=requirements/testing.txt requirements/testing.in
#
certifi==2024.2.2
# via requests
charset-normalizer==3.3.2
# via requests
codecov==2.1.13
# via -r requirements/testing.in
coverage[toml]==7.5.3
# via
# -r requirements/testing.in
# codecov
# pytest-cov
dirty-equals==0.7.1.post0
# via -r requirements/testing.in
idna==3.6
# via requests
iniconfig==2.0.0
# via pytest
markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==23.2
# via pytest
pluggy==1.5.0
# via pytest
pygments==2.17.2
# via rich
pytest==8.2.1
# via
# -r requirements/testing.in
# pytest-cov
# pytest-pretty
pytest-cov==5.0.0
# via -r requirements/testing.in
pytest-pretty==1.2.0
# via -r requirements/testing.in
pytz==2024.1
# via dirty-equals
requests==2.31.0
# via codecov
rich==13.7.0
# via pytest-pretty
urllib3==2.2.1
# via requests

View file

@ -189,7 +189,7 @@ def test_json_schema():
'type': 'object',
}
},
'properties': {'value': {'allOf': [{'$ref': '#/$defs/Coordinate'}], 'title': 'Value'}},
'properties': {'value': {'$ref': '#/$defs/Coordinate', 'title': 'Value'}},
'required': ['value'],
'title': 'Model',
'type': 'object',

View file

@ -25,6 +25,12 @@ def test_ISO4217_code_ok(currency: str):
assert model.model_dump() == {'currency': currency} # test serialization
@pytest.mark.parametrize('currency', ['USD', 'usd', 'UsD'])
def test_ISO4217_code_ok_lower_case(currency: str):
model = ISO4217CheckingModel(currency=currency)
assert model.currency == currency.upper()
@pytest.mark.parametrize(
'currency',
filter(
@ -38,6 +44,12 @@ def test_everyday_code_ok(currency: str):
assert model.model_dump() == {'currency': currency} # test serialization
@pytest.mark.parametrize('currency', ['USD', 'usd', 'UsD'])
def test_everyday_code_ok_lower_case(currency: str):
model = CurrencyCheckingModel(currency=currency)
assert model.currency == currency.upper()
def test_ISO4217_fails():
with pytest.raises(
ValidationError,
@ -56,7 +68,7 @@ def test_forbidden_everyday(forbidden_currency):
ValidationError,
match=re.escape(
'1 validation error for CurrencyCheckingModel\ncurrency\n '
'Invalid currency code. See https://en.wikipedia.org/wiki/ISO_4217. '
'Invalid currency code. See https://en.wikipedia.org/wiki/ISO_4217 . '
'Bonds, testing and precious metals codes are not allowed. '
f"[type=InvalidCurrency, input_value='{forbidden_currency}', input_type=str]"
),

76
tests/test_domain.py Normal file
View file

@ -0,0 +1,76 @@
from typing import Any
import pytest
from pydantic import BaseModel, ValidationError
from pydantic_extra_types.domain import DomainStr
class MyModel(BaseModel):
domain: DomainStr
valid_domains = [
'example.com',
'sub.example.com',
'sub-domain.example-site.co.uk',
'a.com',
'x.com',
'1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.com', # Multiple subdomains
]
invalid_domains = [
'', # Empty string
'example', # Missing TLD
'.com', # Missing domain name
'example.', # Trailing dot
'exam ple.com', # Space in domain
'exa_mple.com', # Underscore in domain
'example.com.', # Trailing dot
]
very_long_domains = [
'a' * 249 + '.com', # Just under the limit
'a' * 250 + '.com', # At the limit
'a' * 251 + '.com', # Just over the limit
'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com',
]
invalid_domain_types = [1, 2, 1.1, 2.1, False, [], {}, None]
@pytest.mark.parametrize('domain', valid_domains)
def test_valid_domains(domain: str):
try:
MyModel.model_validate({'domain': domain})
assert len(domain) < 254 and len(domain) > 0
except ValidationError:
assert len(domain) > 254 or len(domain) == 0
@pytest.mark.parametrize('domain', invalid_domains)
def test_invalid_domains(domain: str):
try:
MyModel.model_validate({'domain': domain})
raise Exception(
f"This test case has only samples that should raise a ValidationError. This domain '{domain}' did not raise such an exception."
)
except ValidationError:
# An error is expected on this test
pass
@pytest.mark.parametrize('domain', very_long_domains)
def test_very_long_domains(domain: str):
try:
MyModel.model_validate({'domain': domain})
assert len(domain) < 254 and len(domain) > 0
except ValidationError:
# An error is expected on this test
pass
@pytest.mark.parametrize('domain', invalid_domain_types)
def test_invalid_domain_types(domain: Any):
with pytest.raises(ValidationError, match='Value must be a string'):
MyModel(domain=domain)

39
tests/test_epoch.py Normal file
View file

@ -0,0 +1,39 @@
import datetime
import pytest
from pydantic_extra_types import epoch
@pytest.mark.parametrize('type_,cls_', [(int, epoch.Integer), (float, epoch.Number)], ids=['integer', 'number'])
def test_type(type_, cls_):
from pydantic import BaseModel
class A(BaseModel):
epoch: cls_
now = datetime.datetime.now(tz=datetime.timezone.utc)
ts = type_(now.timestamp())
a = A.model_validate({'epoch': ts})
v = a.model_dump()
assert v['epoch'] == ts
b = A.model_construct(epoch=now)
v = b.model_dump()
assert v['epoch'] == ts
c = A.model_validate(dict(epoch=ts))
v = c.model_dump()
assert v['epoch'] == ts
@pytest.mark.parametrize('cls_', [(epoch.Integer), (epoch.Number)], ids=['integer', 'number'])
def test_schema(cls_):
from pydantic import BaseModel
class A(BaseModel):
dt: cls_
v = A.model_json_schema()
assert (dt := v['properties']['dt'])['type'] == cls_.TYPE and dt['format'] == 'date-time'

View file

@ -1,23 +1,28 @@
from typing import Union
import pycountry
import pytest
from pydantic import BaseModel
from typing_extensions import Annotated
import pydantic_extra_types
from pydantic_extra_types import epoch
from pydantic_extra_types.color import Color
from pydantic_extra_types.coordinate import Coordinate, Latitude, Longitude
from pydantic_extra_types.country import (
CountryAlpha2,
CountryAlpha3,
CountryNumericCode,
CountryShortName,
)
from pydantic_extra_types.country import CountryAlpha2, CountryAlpha3, CountryNumericCode, CountryShortName
from pydantic_extra_types.currency_code import ISO4217, Currency
from pydantic_extra_types.domain import DomainStr
from pydantic_extra_types.isbn import ISBN
from pydantic_extra_types.language_code import ISO639_3, ISO639_5, LanguageAlpha2, LanguageName
from pydantic_extra_types.mac_address import MacAddress
from pydantic_extra_types.payment import PaymentCardNumber
from pydantic_extra_types.pendulum_dt import DateTime
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
from pydantic_extra_types.s3 import S3Path
from pydantic_extra_types.script_code import ISO_15924
from pydantic_extra_types.semantic_version import SemanticVersion
from pydantic_extra_types.semver import _VersionPydanticAnnotation
from pydantic_extra_types.timezone_name import TimeZoneName
from pydantic_extra_types.ulid import ULID
languages = [lang.alpha_3 for lang in pycountry.languages]
@ -35,8 +40,20 @@ everyday_currencies = [
scripts = [script.alpha_4 for script in pycountry.scripts]
timezone_names = TimeZoneName.allowed_values_list
everyday_currencies.sort()
AnyNumberRFC3966 = Annotated[Union[str, PhoneNumber], PhoneNumberValidator()]
USNumberE164 = Annotated[
Union[str, PhoneNumber],
PhoneNumberValidator(
supported_regions=['US'],
default_region='US',
number_format='E164',
),
]
@pytest.mark.parametrize(
'cls,expected',
@ -325,6 +342,158 @@ everyday_currencies.sort()
'type': 'object',
},
),
(
SemanticVersion,
{
'properties': {
'x': {
'title': 'X',
'type': 'string',
'pattern': r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$',
}
},
'required': ['x'],
'title': 'Model',
'type': 'object',
},
),
(
TimeZoneName,
{
'properties': {
'x': {
'title': 'X',
'type': 'string',
'enum': timezone_names,
'minLength': 1,
}
},
'required': ['x'],
'title': 'Model',
'type': 'object',
},
),
(
_VersionPydanticAnnotation,
{
'properties': {'x': {'title': 'X', 'type': 'string'}},
'required': ['x'],
'title': 'Model',
'type': 'object',
},
),
(
PhoneNumber,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'type': 'string',
'format': 'phone',
}
},
'required': ['x'],
},
),
(
AnyNumberRFC3966,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'type': 'string',
'format': 'phone',
}
},
'required': ['x'],
},
),
(
USNumberE164,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'type': 'string',
'format': 'phone',
}
},
'required': ['x'],
},
),
(
S3Path,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'pattern': '^s3://([^/]+)/(.*?([^/]+)/?)$',
'title': 'X',
'type': 'string',
},
},
'required': [
'x',
],
},
),
(
DomainStr,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'type': 'string',
},
},
'required': [
'x',
],
},
),
(
epoch.Integer,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'type': 'integer',
'format': 'date-time',
},
},
'required': [
'x',
],
},
),
(
epoch.Number,
{
'title': 'Model',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'type': 'number',
'format': 'date-time',
},
},
'required': [
'x',
],
},
),
],
)
def test_json_schema(cls, expected):

View file

@ -41,9 +41,7 @@ class DurationModel(BaseModel):
],
)
def test_existing_instance(instance):
"""
Verifies that constructing a model with an existing pendulum dt doesn't throw.
"""
"""Verifies that constructing a model with an existing pendulum dt doesn't throw."""
model = DtModel(dt=instance)
if isinstance(instance, datetime):
assert model.dt == pendulum.instance(instance)
@ -75,9 +73,7 @@ def test_existing_instance(instance):
],
)
def test_pendulum_date_existing_instance(instance):
"""
Verifies that constructing a model with an existing pendulum date doesn't throw.
"""
"""Verifies that constructing a model with an existing pendulum date doesn't throw."""
model = DateModel(d=instance)
if isinstance(instance, datetime):
assert model.d == pendulum.instance(instance).date()
@ -96,14 +92,34 @@ def test_pendulum_date_existing_instance(instance):
[
pendulum.duration(days=42, hours=13, minutes=37),
pendulum.duration(days=-42, hours=13, minutes=37),
pendulum.duration(weeks=97),
pendulum.duration(days=463),
pendulum.duration(milliseconds=90122),
pendulum.duration(microseconds=90122),
pendulum.duration(
years=2,
months=3,
weeks=19,
days=1,
hours=25,
seconds=732,
milliseconds=123,
microseconds=1324,
),
timedelta(days=42, hours=13, minutes=37),
timedelta(days=-42, hours=13, minutes=37),
timedelta(
weeks=19,
days=1,
hours=25,
seconds=732,
milliseconds=123,
microseconds=1324,
),
],
)
def test_duration_timedelta__existing_instance(instance):
"""
Verifies that constructing a model with an existing pendulum duration doesn't throw.
"""
"""Verifies that constructing a model with an existing pendulum duration doesn't throw."""
model = DurationModel(delta_t=instance)
assert model.delta_t.total_seconds() == instance.total_seconds()
@ -119,9 +135,7 @@ def test_duration_timedelta__existing_instance(instance):
],
)
def test_pendulum_dt_from_serialized(dt):
"""
Verifies that building an instance from serialized, well-formed strings decode properly.
"""
"""Verifies that building an instance from serialized, well-formed strings decode properly."""
dt_actual = pendulum.parse(dt)
model = DtModel(dt=dt)
assert model.dt == dt_actual
@ -137,8 +151,7 @@ def test_pendulum_dt_from_serialized(dt):
],
)
def test_pendulum_dt_from_serialized_preserves_timezones(dt):
"""
Verifies that building an instance from serialized, well-formed strings decode
"""Verifies that building an instance from serialized, well-formed strings decode
properly and preserves the timezone information across all of the Pendulum DateTime
properties. Regression test for pydantic/pydantic-extra-types#188.
"""
@ -165,9 +178,7 @@ def test_pendulum_dt_from_serialized_preserves_timezones(dt):
],
)
def test_pendulum_dt_not_strict_from_serialized(dt):
"""
Verifies that building an instance from serialized, well-formed strings decode properly.
"""
"""Verifies that building an instance from serialized, well-formed strings decode properly."""
dt_actual = pendulum.parse(dt, strict=False)
model = DtModelNotStrict(dt=dt)
assert model.dt == dt_actual
@ -200,9 +211,7 @@ def test_pendulum_dt_not_strict_from_serialized(dt):
],
)
def test_pendulum_dt_from_str_unix_timestamp(dt):
"""
Verifies that building an instance from serialized, well-formed strings decode properly.
"""
"""Verifies that building an instance from serialized, well-formed strings decode properly."""
dt_actual = pendulum.instance(DtTypeAdapter.validate_python(dt))
model = DtModel(dt=dt)
assert model.dt == dt_actual
@ -233,21 +242,21 @@ def test_pendulum_dt_from_str_unix_timestamp(dt):
],
)
def test_pendulum_dt_from_str_unix_timestamp_is_utc(dt):
"""
Verifies that without timezone information, it is coerced to UTC. As in pendulum
"""
"""Verifies that without timezone information, it is coerced to UTC. As in pendulum"""
model = DtModel(dt=dt)
assert model.dt.tzinfo.tzname(model.dt) == 'UTC'
@pytest.mark.parametrize(
'd',
[pendulum.now().date().isoformat(), pendulum.now().to_w3c_string(), pendulum.now().to_iso8601_string()],
[
pendulum.now().date().isoformat(),
pendulum.now().to_w3c_string(),
pendulum.now().to_iso8601_string(),
],
)
def test_pendulum_date_from_serialized(d):
"""
Verifies that building an instance from serialized, well-formed strings decode properly.
"""
"""Verifies that building an instance from serialized, well-formed strings decode properly."""
date_actual = pendulum.parse(d).date()
model = DateModel(d=d)
assert model.d == date_actual
@ -266,9 +275,7 @@ def test_pendulum_date_from_serialized(d):
],
)
def test_pendulum_duration_from_serialized(delta_t_str):
"""
Verifies that building an instance from serialized, well-formed strings decode properly.
"""
"""Verifies that building an instance from serialized, well-formed strings decode properly."""
true_delta_t = pendulum.parse(delta_t_str)
model = DurationModel(delta_t=delta_t_str)
assert model.delta_t == true_delta_t
@ -315,39 +322,41 @@ dt_strict.append(pendulum.now().to_iso8601_string()[:5])
dt_strict,
)
def test_pendulum_dt_malformed(dt):
"""
Verifies that the instance fails to validate if malformed dt is passed.
"""
"""Verifies that the instance fails to validate if malformed dt is passed."""
with pytest.raises(ValidationError):
DtModel(dt=dt)
@pytest.mark.parametrize('dt', get_invalid_dt_common())
def test_pendulum_dt_non_strict_malformed(dt):
"""
Verifies that the instance fails to validate if malformed dt are passed.
"""
"""Verifies that the instance fails to validate if malformed dt are passed."""
with pytest.raises(ValidationError):
DtModelNotStrict(dt=dt)
@pytest.mark.parametrize('invalid_value', [None, 'malformed', pendulum.today().to_iso8601_string()[:5], 'P10Y10M10D'])
@pytest.mark.parametrize(
'invalid_value',
[None, 'malformed', pendulum.today().to_iso8601_string()[:5], 'P10Y10M10D'],
)
def test_pendulum_date_malformed(invalid_value):
"""
Verifies that the instance fails to validate if malformed date are passed.
"""
"""Verifies that the instance fails to validate if malformed date are passed."""
with pytest.raises(ValidationError):
DateModel(d=invalid_value)
@pytest.mark.parametrize(
'delta_t',
[None, 'malformed', pendulum.today().to_iso8601_string()[:5], 42, '12m', '2021-01-01T12:00:00'],
[
None,
'malformed',
pendulum.today().to_iso8601_string()[:5],
42,
'12m',
'2021-01-01T12:00:00',
],
)
def test_pendulum_duration_malformed(delta_t):
"""
Verifies that the instance fails to validate if malformed durations are passed.
"""
"""Verifies that the instance fails to validate if malformed durations are passed."""
with pytest.raises(ValidationError):
DurationModel(delta_t=delta_t)
@ -371,3 +380,9 @@ def test_date_type_adapter(input_type: type, value, is_instance: type):
assert type(validated) is input_type
assert isinstance(validated, input_type)
assert isinstance(validated, is_instance)
def test_pendulum_duration_months_are_preserved():
m = DurationModel(delta_t=pendulum.Duration(months=1))
assert m.delta_t.months == 1

View file

@ -31,15 +31,21 @@ def test_formats_phone_number() -> None:
def test_supported_regions() -> None:
assert 'US' in PhoneNumber.supported_regions
assert 'GB' in PhoneNumber.supported_regions
assert PhoneNumber.supported_regions == []
PhoneNumber.supported_regions = ['US']
assert Something(phone_number='+1 901 555 1212')
def test_supported_formats() -> None:
assert 'E164' in PhoneNumber.supported_formats
assert 'RFC3966' in PhoneNumber.supported_formats
assert '__dict__' not in PhoneNumber.supported_formats
assert 'to_string' not in PhoneNumber.supported_formats
with pytest.raises(ValidationError, match='value is not from a supported region'):
Something(phone_number='+44 20 7946 0958')
USPhoneNumber = PhoneNumber()
USPhoneNumber.supported_regions = ['US']
assert USPhoneNumber.supported_regions == ['US']
assert Something(phone_number='+1 901 555 1212')
with pytest.raises(ValidationError, match='value is not from a supported region'):
Something(phone_number='+44 20 7946 0958')
def test_parse_error() -> None:
@ -64,20 +70,3 @@ def test_eq() -> None:
assert PhoneNumber('555-1212') == '555-1212'
assert PhoneNumber('555-1212') != '555-1213'
assert PhoneNumber('555-1212') != PhoneNumber('555-1213')
def test_json_schema() -> None:
assert Something.model_json_schema() == {
'title': 'Something',
'type': 'object',
'properties': {
'phone_number': {
'title': 'Phone Number',
'type': 'string',
'format': 'phone',
'minLength': 7,
'maxLength': 64,
}
},
'required': ['phone_number'],
}

View file

@ -0,0 +1,102 @@
from typing import Any, Optional, Union
import phonenumbers
import pytest
from phonenumbers import PhoneNumber
from pydantic import BaseModel, TypeAdapter, ValidationError
from typing_extensions import Annotated
from pydantic_extra_types.phone_numbers import PhoneNumberValidator
Number = Annotated[Union[str, PhoneNumber], PhoneNumberValidator()]
NANumber = Annotated[
Union[str, PhoneNumber],
PhoneNumberValidator(
supported_regions=['US', 'CA'],
default_region='US',
),
]
UKNumber = Annotated[
Union[str, PhoneNumber],
PhoneNumberValidator(
supported_regions=['GB'],
default_region='GB',
number_format='E164',
),
]
number_adapter = TypeAdapter(Number)
class Numbers(BaseModel):
phone_number: Optional[Number] = None
na_number: Optional[NANumber] = None
uk_number: Optional[UKNumber] = None
def test_validator_constructor() -> None:
PhoneNumberValidator()
PhoneNumberValidator(supported_regions=['US', 'CA'], default_region='US')
PhoneNumberValidator(supported_regions=['GB'], default_region='GB', number_format='E164')
with pytest.raises(ValueError, match='Invalid default region code: XX'):
PhoneNumberValidator(default_region='XX')
with pytest.raises(ValueError, match='Invalid number format: XX'):
PhoneNumberValidator(number_format='XX')
with pytest.raises(ValueError, match='Invalid supported region code: XX'):
PhoneNumberValidator(supported_regions=['XX'])
# Note: the 555 area code will result in an invalid phone number
def test_valid_phone_number() -> None:
Numbers(phone_number='+1 901 555 1212')
def test_when_extension_provided() -> None:
Numbers(phone_number='+1 901 555 1212 ext 12533')
def test_when_phonenumber_instance() -> None:
phone_number = phonenumbers.parse('+1 901 555 1212', region='US')
numbers = Numbers(phone_number=phone_number)
assert numbers.phone_number == 'tel:+1-901-555-1212'
# Additional validation is still performed on the instance
with pytest.raises(ValidationError, match='value is not from a supported region'):
Numbers(uk_number=phone_number)
@pytest.mark.parametrize('invalid_number', ['', '123', 12, object(), '55 121'])
def test_invalid_phone_number(invalid_number: Any) -> None:
# Use a TypeAdapter to test the validation logic for None otherwise
# optional fields will not attempt to validate
with pytest.raises(ValidationError, match='value is not a valid phone number'):
number_adapter.validate_python(invalid_number)
def test_formats_phone_number() -> None:
result = Numbers(phone_number='+1 901 555 1212 ext 12533', uk_number='+44 20 7946 0958')
assert result.phone_number == 'tel:+1-901-555-1212;ext=12533'
assert result.uk_number == '+442079460958'
def test_default_region() -> None:
result = Numbers(na_number='901 555 1212')
assert result.na_number == 'tel:+1-901-555-1212'
with pytest.raises(ValidationError, match='value is not a valid phone number'):
Numbers(phone_number='901 555 1212')
def test_supported_regions() -> None:
assert Numbers(na_number='+1 901 555 1212')
assert Numbers(uk_number='+44 20 7946 0958')
with pytest.raises(ValidationError, match='value is not from a supported region'):
Numbers(na_number='+44 20 7946 0958')
def test_parse_error() -> None:
with pytest.raises(ValidationError, match='value is not a valid phone number'):
Numbers(phone_number='555 1212')
def test_parsed_but_not_a_valid_number() -> None:
with pytest.raises(ValidationError, match='value is not a valid phone number'):
Numbers(phone_number='+1 555-1212')

152
tests/test_s3.py Normal file
View file

@ -0,0 +1,152 @@
import pytest
from pydantic import BaseModel, ValidationError
from pydantic_extra_types.s3 import S3Path
class S3Check(BaseModel):
path: S3Path
@pytest.mark.parametrize(
'raw,bucket,key,last_key',
[
(
's3://my-data-bucket/2023/08/29/sales-report.csv',
'my-data-bucket',
'2023/08/29/sales-report.csv',
'sales-report.csv',
),
(
's3://logs-bucket/app-logs/production/2024/07/01/application-log.txt',
'logs-bucket',
'app-logs/production/2024/07/01/application-log.txt',
'application-log.txt',
),
(
's3://backup-storage/user_data/john_doe/photos/photo-2024-08-15.jpg',
'backup-storage',
'user_data/john_doe/photos/photo-2024-08-15.jpg',
'photo-2024-08-15.jpg',
),
(
's3://analytics-bucket/weekly-reports/Q3/2023/week-35-summary.pdf',
'analytics-bucket',
'weekly-reports/Q3/2023/week-35-summary.pdf',
'week-35-summary.pdf',
),
(
's3://project-data/docs/presentations/quarterly_review.pptx',
'project-data',
'docs/presentations/quarterly_review.pptx',
'quarterly_review.pptx',
),
(
's3://my-music-archive/genres/rock/2024/favorite-songs.mp3',
'my-music-archive',
'genres/rock/2024/favorite-songs.mp3',
'favorite-songs.mp3',
),
(
's3://video-uploads/movies/2024/03/action/thriller/movie-trailer.mp4',
'video-uploads',
'movies/2024/03/action/thriller/movie-trailer.mp4',
'movie-trailer.mp4',
),
(
's3://company-files/legal/contracts/contract-2023-09-01.pdf',
'company-files',
'legal/contracts/contract-2023-09-01.pdf',
'contract-2023-09-01.pdf',
),
(
's3://dev-environment/source-code/release_v1.0.2.zip',
'dev-environment',
'source-code/release_v1.0.2.zip',
'release_v1.0.2.zip',
),
(
's3://public-bucket/open-data/geojson/maps/city_boundaries.geojson',
'public-bucket',
'open-data/geojson/maps/city_boundaries.geojson',
'city_boundaries.geojson',
),
(
's3://image-storage/2024/portfolio/shoots/wedding/couple_photo_12.jpg',
'image-storage',
'2024/portfolio/shoots/wedding/couple_photo_12.jpg',
'couple_photo_12.jpg',
),
(
's3://finance-data/reports/2024/Q2/income_statement.xlsx',
'finance-data',
'reports/2024/Q2/income_statement.xlsx',
'income_statement.xlsx',
),
(
's3://training-data/nlp/corpora/english/2023/text_corpus.txt',
'training-data',
'nlp/corpora/english/2023/text_corpus.txt',
'text_corpus.txt',
),
(
's3://ecommerce-backup/2024/transactions/august/orders_2024_08_28.csv',
'ecommerce-backup',
'2024/transactions/august/orders_2024_08_28.csv',
'orders_2024_08_28.csv',
),
(
's3://gaming-assets/3d_models/characters/hero/model_v5.obj',
'gaming-assets',
'3d_models/characters/hero/model_v5.obj',
'model_v5.obj',
),
(
's3://iot-sensor-data/2024/temperature_sensors/sensor_42_readings.csv',
'iot-sensor-data',
'2024/temperature_sensors/sensor_42_readings.csv',
'sensor_42_readings.csv',
),
(
's3://user-uploads/avatars/user123/avatar_2024_08_29.png',
'user-uploads',
'avatars/user123/avatar_2024_08_29.png',
'avatar_2024_08_29.png',
),
(
's3://media-library/podcasts/2023/episode_45.mp3',
'media-library',
'podcasts/2023/episode_45.mp3',
'episode_45.mp3',
),
(
's3://logs-bucket/security/firewall-logs/2024/08/failed_attempts.log',
'logs-bucket',
'security/firewall-logs/2024/08/failed_attempts.log',
'failed_attempts.log',
),
(
's3://data-warehouse/financials/quarterly/2024/Q1/profit_loss.csv',
'data-warehouse',
'financials/quarterly/2024/Q1/profit_loss.csv',
'profit_loss.csv',
),
(
's3://data-warehouse/financials/quarterly/2024/Q1',
'data-warehouse',
'financials/quarterly/2024/Q1',
'Q1',
),
],
)
def test_s3(raw: str, bucket: str, key: str, last_key: str):
model = S3Check(path=raw)
assert model.path == S3Path(raw)
assert model.path.bucket == bucket
assert model.path.key == key
assert model.path.last_key == last_key
def test_wrong_s3():
with pytest.raises(ValidationError):
S3Check(path='s3/ok')

View file

@ -0,0 +1,109 @@
import pytest
import semver
from pydantic import BaseModel, ValidationError
from pydantic_extra_types.semantic_version import SemanticVersion
@pytest.fixture(scope='module', name='SemanticVersionObject')
def application_object_fixture():
class Application(BaseModel):
version: SemanticVersion
return Application
@pytest.mark.parametrize(
'constructor', [str, semver.Version.parse, SemanticVersion.parse], ids=['str', 'semver.Version', 'SemanticVersion']
)
@pytest.mark.parametrize(
'version',
[
'0.0.4',
'1.2.3',
'10.20.30',
'1.1.2-prerelease+meta',
'1.1.2+meta',
'1.1.2+meta-valid',
'1.0.0-alpha',
'1.0.0-beta',
'1.0.0-alpha.beta',
'1.0.0-alpha.beta.1',
'1.0.0-alpha.1',
'1.0.0-alpha0.valid',
'1.0.0-alpha.0valid',
'1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay',
'1.0.0-rc.1+build.1',
'2.0.0-rc.1+build.123',
'1.2.3-beta',
'10.2.3-DEV-SNAPSHOT',
'1.2.3-SNAPSHOT-123',
'1.0.0',
'2.0.0',
'1.1.7',
'2.0.0+build.1848',
'2.0.1-alpha.1227',
'1.0.0-alpha+beta',
'1.2.3----RC-SNAPSHOT.12.9.1--.12+788',
'1.2.3----R-S.12.9.1--.12+meta',
'1.2.3----RC-SNAPSHOT.12.9.1--.12',
'1.0.0+0.build.1-rc.10000aaa-kk-0.1',
'99999999999999999999999.999999999999999999.99999999999999999',
'1.0.0-0A.is.legal',
],
)
def test_valid_semantic_version(SemanticVersionObject, constructor, version):
application = SemanticVersionObject(version=constructor(version))
assert application.version
assert application.model_dump() == {'version': version}
@pytest.mark.parametrize(
'invalid_version',
[
'',
'1',
'1.2',
'1.2.3-0123',
'1.2.3-0123.0123',
'1.1.2+.123',
'+invalid',
'-invalid',
'-invalid+invalid',
'-invalid.01',
'alpha',
'alpha.beta',
'alpha.beta.1',
'alpha.1',
'alpha+beta',
'alpha_beta',
'alpha.',
'alpha..',
'beta',
'1.0.0-alpha_beta',
'-alpha.',
'1.0.0-alpha..',
'1.0.0-alpha..1',
'1.0.0-alpha...1',
'1.0.0-alpha....1',
'1.0.0-alpha.....1',
'1.0.0-alpha......1',
'1.0.0-alpha.......1',
'01.1.1',
'1.01.1',
'1.1.01',
'1.2',
'1.2.3.DEV',
'1.2-SNAPSHOT',
'1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788',
'1.2-RC-SNAPSHOT',
'-1.0.3-gamma+b7718',
'+justmeta',
'9.8.7+meta+meta',
'9.8.7-whatever+meta+meta',
'99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12',
],
)
def test_invalid_semantic_version(SemanticVersionObject, invalid_version):
with pytest.raises(ValidationError):
SemanticVersionObject(version=invalid_version)

21
tests/test_semver.py Normal file
View file

@ -0,0 +1,21 @@
import pytest
from pydantic import BaseModel
from pydantic_extra_types.semver import _VersionPydanticAnnotation
class SomethingWithAVersion(BaseModel):
version: _VersionPydanticAnnotation
def test_valid_semver() -> None:
SomethingWithAVersion(version='1.2.3')
def test_valid_semver_with_prerelease() -> None:
SomethingWithAVersion(version='1.2.3-alpha.1')
def test_invalid_semver() -> None:
with pytest.raises(ValueError):
SomethingWithAVersion(version='jim.was.here')

View file

@ -0,0 +1,209 @@
import re
import pytest
import pytz
from pydantic import BaseModel, ValidationError
from pydantic_core import PydanticCustomError
from pydantic_extra_types.timezone_name import TimeZoneName, TimeZoneNameSettings, timezone_name_settings
has_zone_info = True
try:
from zoneinfo import available_timezones
except ImportError:
has_zone_info = False
pytz_zones_bad = [(zone.lower(), zone) for zone in pytz.all_timezones]
pytz_zones_bad.extend([(f' {zone}', zone) for zone in pytz.all_timezones_set])
class TZNameCheck(BaseModel):
timezone_name: TimeZoneName
@timezone_name_settings(strict=False)
class TZNonStrict(TimeZoneName):
pass
class NonStrictTzName(BaseModel):
timezone_name: TZNonStrict
@pytest.mark.parametrize('zone', pytz.all_timezones)
def test_all_timezones_non_strict_pytz(zone):
assert TZNameCheck(timezone_name=zone).timezone_name == zone
assert NonStrictTzName(timezone_name=zone).timezone_name == zone
@pytest.mark.parametrize('zone', pytz_zones_bad)
def test_all_timezones_pytz_lower(zone):
assert NonStrictTzName(timezone_name=zone[0]).timezone_name == zone[1]
def test_fail_non_existing_timezone():
with pytest.raises(
ValidationError,
match=re.escape(
'1 validation error for TZNameCheck\n'
'timezone_name\n '
'Invalid timezone name. '
"[type=TimeZoneName, input_value='mars', input_type=str]"
),
):
TZNameCheck(timezone_name='mars')
with pytest.raises(
ValidationError,
match=re.escape(
'1 validation error for NonStrictTzName\n'
'timezone_name\n '
'Invalid timezone name. '
"[type=TimeZoneName, input_value='mars', input_type=str]"
),
):
NonStrictTzName(timezone_name='mars')
if has_zone_info:
zones = list(available_timezones())
zones.sort()
zones_bad = [(zone.lower(), zone) for zone in zones]
@pytest.mark.parametrize('zone', zones)
def test_all_timezones_zone_info(zone):
assert TZNameCheck(timezone_name=zone).timezone_name == zone
assert NonStrictTzName(timezone_name=zone).timezone_name == zone
@pytest.mark.parametrize('zone', zones_bad)
def test_all_timezones_zone_info_NonStrict(zone):
assert NonStrictTzName(timezone_name=zone[0]).timezone_name == zone[1]
def test_timezone_name_settings_metaclass():
class TestStrictTZ(TimeZoneName, strict=True, metaclass=TimeZoneNameSettings):
pass
class TestNonStrictTZ(TimeZoneName, strict=False, metaclass=TimeZoneNameSettings):
pass
assert TestStrictTZ.strict is True
assert TestNonStrictTZ.strict is False
# Test default value
class TestDefaultStrictTZ(TimeZoneName, metaclass=TimeZoneNameSettings):
pass
assert TestDefaultStrictTZ.strict is True
def test_timezone_name_validation():
valid_tz = 'America/New_York'
invalid_tz = 'Invalid/Timezone'
assert TimeZoneName._validate(valid_tz, None) == valid_tz
with pytest.raises(PydanticCustomError):
TimeZoneName._validate(invalid_tz, None)
assert TZNonStrict._validate(valid_tz.lower(), None) == valid_tz
assert TZNonStrict._validate(f' {valid_tz} ', None) == valid_tz
with pytest.raises(PydanticCustomError):
TZNonStrict._validate(invalid_tz, None)
def test_timezone_name_pydantic_core_schema():
schema = TimeZoneName.__get_pydantic_core_schema__(TimeZoneName, None)
assert isinstance(schema, dict)
assert schema['type'] == 'function-after'
assert 'function' in schema
assert 'schema' in schema
assert schema['schema']['type'] == 'str'
assert schema['schema']['min_length'] == 1
def test_timezone_name_pydantic_json_schema():
core_schema = TimeZoneName.__get_pydantic_core_schema__(TimeZoneName, None)
class MockJsonSchemaHandler:
def __call__(self, schema):
return {'type': 'string'}
handler = MockJsonSchemaHandler()
json_schema = TimeZoneName.__get_pydantic_json_schema__(core_schema, handler)
assert 'enum' in json_schema
assert isinstance(json_schema['enum'], list)
assert len(json_schema['enum']) > 0
def test_timezone_name_repr():
tz = TimeZoneName('America/New_York')
assert repr(tz) == "'America/New_York'"
assert str(tz) == 'America/New_York'
def test_timezone_name_allowed_values():
assert isinstance(TimeZoneName.allowed_values, set)
assert len(TimeZoneName.allowed_values) > 0
assert all(isinstance(tz, str) for tz in TimeZoneName.allowed_values)
assert isinstance(TimeZoneName.allowed_values_list, list)
assert len(TimeZoneName.allowed_values_list) > 0
assert all(isinstance(tz, str) for tz in TimeZoneName.allowed_values_list)
assert isinstance(TimeZoneName.allowed_values_upper_to_correct, dict)
assert len(TimeZoneName.allowed_values_upper_to_correct) > 0
assert all(
isinstance(k, str) and isinstance(v, str) for k, v in TimeZoneName.allowed_values_upper_to_correct.items()
)
def test_timezone_name_inheritance():
class CustomTZ(TimeZoneName, metaclass=TimeZoneNameSettings):
pass
assert issubclass(CustomTZ, TimeZoneName)
assert issubclass(CustomTZ, str)
assert isinstance(CustomTZ('America/New_York'), (CustomTZ, TimeZoneName, str))
def test_timezone_name_string_operations():
tz = TimeZoneName('America/New_York')
assert tz.upper() == 'AMERICA/NEW_YORK'
assert tz.lower() == 'america/new_york'
assert tz.strip() == 'America/New_York'
assert f'{tz} Time' == 'America/New_York Time'
assert tz.startswith('America')
assert tz.endswith('York')
def test_timezone_name_comparison():
tz1 = TimeZoneName('America/New_York')
tz2 = TimeZoneName('Europe/London')
tz3 = TimeZoneName('America/New_York')
assert tz1 == tz3
assert tz1 != tz2
assert tz1 < tz2 # Alphabetical comparison
assert tz2 > tz1
assert tz1 <= tz3
assert tz1 >= tz3
def test_timezone_name_hash():
tz1 = TimeZoneName('America/New_York')
tz2 = TimeZoneName('America/New_York')
tz3 = TimeZoneName('Europe/London')
assert hash(tz1) == hash(tz2)
assert hash(tz1) != hash(tz3)
tz_set = {tz1, tz2, tz3}
assert len(tz_set) == 2
def test_timezone_name_slots():
tz = TimeZoneName('America/New_York')
with pytest.raises(AttributeError):
tz.new_attribute = 'test'

828
uv.lock generated Normal file
View file

@ -0,0 +1,828 @@
version = 1
requires-python = ">=3.8"
resolution-markers = [
"python_full_version < '3.9'",
"python_full_version >= '3.9' and python_full_version < '3.13'",
"python_full_version >= '3.13'",
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
]
[[package]]
name = "backports-zoneinfo"
version = "0.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ad/85/475e514c3140937cf435954f78dedea1861aeab7662d11de232bdaa90655/backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2", size = 74098 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/6d/eca004eeadcbf8bd64cc96feb9e355536147f0577420b44d80c7cac70767/backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987", size = 35816 },
{ url = "https://files.pythonhosted.org/packages/c1/8f/9b1b920a6a95652463143943fa3b8c000cb0b932ab463764a6f2a2416560/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1", size = 72147 },
{ url = "https://files.pythonhosted.org/packages/1a/ab/3e941e3fcf1b7d3ab3d0233194d99d6a0ed6b24f8f956fc81e47edc8c079/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9", size = 74033 },
{ url = "https://files.pythonhosted.org/packages/c0/34/5fdb0a3a28841d215c255be8fc60b8666257bb6632193c86fd04b63d4a31/backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328", size = 36803 },
{ url = "https://files.pythonhosted.org/packages/78/cc/e27fd6493bbce8dbea7e6c1bc861fe3d3bc22c4f7c81f4c3befb8ff5bfaf/backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6", size = 38967 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
]
[[package]]
name = "coverage"
version = "7.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690 },
{ url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127 },
{ url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654 },
{ url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598 },
{ url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732 },
{ url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816 },
{ url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325 },
{ url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418 },
{ url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343 },
{ url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136 },
{ url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796 },
{ url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244 },
{ url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279 },
{ url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859 },
{ url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549 },
{ url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477 },
{ url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134 },
{ url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910 },
{ url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348 },
{ url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230 },
{ url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 },
{ url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 },
{ url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 },
{ url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 },
{ url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455 },
{ url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 },
{ url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 },
{ url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 },
{ url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 },
{ url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 },
{ url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 },
{ url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 },
{ url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 },
{ url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 },
{ url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 },
{ url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 },
{ url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 },
{ url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 },
{ url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 },
{ url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 },
{ url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 },
{ url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 },
{ url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 },
{ url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 },
{ url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 },
{ url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 },
{ url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 },
{ url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 },
{ url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 },
{ url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 },
{ url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674 },
{ url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101 },
{ url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554 },
{ url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440 },
{ url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889 },
{ url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142 },
{ url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805 },
{ url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655 },
{ url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296 },
{ url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137 },
{ url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688 },
{ url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120 },
{ url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249 },
{ url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237 },
{ url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311 },
{ url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453 },
{ url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958 },
{ url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938 },
{ url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352 },
{ url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153 },
{ url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926 },
]
[package.optional-dependencies]
toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]]
name = "dirty-equals"
version = "0.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytz", marker = "python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/4c/39c428b7c900b21c8116d89a56b73f6dc14a2455767961b54adfe7c224fe/dirty_equals-0.8.0.tar.gz", hash = "sha256:798db3b9481b9a5024c0e520946507676ed2f0c65317d3e95bdce1a01657cf60", size = 50294 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/cd/8c3ce82cc6b18e149bff3cf8dd50a75316ca093ae706f0c1c4df87f2b88f/dirty_equals-0.8.0-py3-none-any.whl", hash = "sha256:0ef998ba3c395e03cf5eb3cd1c13c26a9a992efa18c0d59c22ba27344519cee1", size = 28217 },
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
]
[[package]]
name = "importlib-resources"
version = "6.4.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp", marker = "python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 },
]
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
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 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
]
[[package]]
name = "mypy"
version = "1.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/8c/206de95a27722b5b5a8c85ba3100467bd86299d92a4f71c6b9aa448bfa2f/mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", size = 11020731 },
{ url = "https://files.pythonhosted.org/packages/ab/bb/b31695a29eea76b1569fd28b4ab141a1adc9842edde080d1e8e1776862c7/mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", size = 10184276 },
{ url = "https://files.pythonhosted.org/packages/a5/2d/4a23849729bb27934a0e079c9c1aad912167d875c7b070382a408d459651/mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", size = 12587706 },
{ url = "https://files.pythonhosted.org/packages/5c/c3/d318e38ada50255e22e23353a469c791379825240e71b0ad03e76ca07ae6/mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", size = 13105586 },
{ url = "https://files.pythonhosted.org/packages/4a/25/3918bc64952370c3dbdbd8c82c363804678127815febd2925b7273d9482c/mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", size = 9632318 },
{ url = "https://files.pythonhosted.org/packages/d0/19/de0822609e5b93d02579075248c7aa6ceaddcea92f00bf4ea8e4c22e3598/mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", size = 10939027 },
{ url = "https://files.pythonhosted.org/packages/c8/71/6950fcc6ca84179137e4cbf7cf41e6b68b4a339a1f5d3e954f8c34e02d66/mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", size = 10108699 },
{ url = "https://files.pythonhosted.org/packages/26/50/29d3e7dd166e74dc13d46050b23f7d6d7533acf48f5217663a3719db024e/mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", size = 12506263 },
{ url = "https://files.pythonhosted.org/packages/3f/1d/676e76f07f7d5ddcd4227af3938a9c9640f293b7d8a44dd4ff41d4db25c1/mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", size = 12984688 },
{ url = "https://files.pythonhosted.org/packages/9c/03/5a85a30ae5407b1d28fab51bd3e2103e52ad0918d1e68f02a7778669a307/mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", size = 9626811 },
{ url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 },
{ url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 },
{ url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 },
{ url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 },
{ url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 },
{ url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 },
{ url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 },
{ url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 },
{ url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 },
{ url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 },
{ url = "https://files.pythonhosted.org/packages/5e/2a/13e9ad339131c0fba5c70584f639005a47088f5eed77081a3d00479df0ca/mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a", size = 10955147 },
{ url = "https://files.pythonhosted.org/packages/94/39/02929067dc16b72d78109195cfed349ac4ec85f3d52517ac62b9a5263685/mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb", size = 10138373 },
{ url = "https://files.pythonhosted.org/packages/4a/cc/066709bb01734e3dbbd1375749f8789bf9693f8b842344fc0cf52109694f/mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b", size = 12543621 },
{ url = "https://files.pythonhosted.org/packages/f5/a2/124df839025348c7b9877d0ce134832a9249968e3ab36bb826bab0e9a1cf/mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74", size = 13050348 },
{ url = "https://files.pythonhosted.org/packages/45/86/cc94b1e7f7e756a63043cf425c24fb7470013ee1c032180282db75b1b335/mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6", size = 9615311 },
{ url = "https://files.pythonhosted.org/packages/5f/d4/b33ddd40dad230efb317898a2d1c267c04edba73bc5086bf77edeb410fb2/mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc", size = 11013906 },
{ url = "https://files.pythonhosted.org/packages/f4/e6/f414bca465b44d01cd5f4a82761e15044bedd1bf8025c5af3cc64518fac5/mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732", size = 10180657 },
{ url = "https://files.pythonhosted.org/packages/38/e9/fc3865e417722f98d58409770be01afb961e2c1f99930659ff4ae7ca8b7e/mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc", size = 12586394 },
{ url = "https://files.pythonhosted.org/packages/2e/35/f4d8b6d2cb0b3dad63e96caf159419dda023f45a358c6c9ac582ccaee354/mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d", size = 13103591 },
{ url = "https://files.pythonhosted.org/packages/22/1d/80594aef135f921dd52e142fa0acd19df197690bd0cde42cea7b88cf5aa2/mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24", size = 9634690 },
{ url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 },
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
[[package]]
name = "pendulum"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backports-zoneinfo", marker = "python_full_version < '3.9'" },
{ name = "importlib-resources", marker = "python_full_version < '3.9'" },
{ name = "python-dateutil" },
{ name = "time-machine", marker = "implementation_name != 'pypy'" },
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b8/fe/27c7438c6ac8b8f8bef3c6e571855602ee784b85d072efddfff0ceb1cd77/pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e", size = 84524 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/2f/2f4719366d16f1e444b4e400d3de5021bc4b09965f97e45c81e08348cbdf/pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd", size = 362284 },
{ url = "https://files.pythonhosted.org/packages/30/ff/70a8f47e622e641de15b7ed8a8b66c3aa895fabc182a7d520a0c33ec850e/pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6", size = 352957 },
{ url = "https://files.pythonhosted.org/packages/f4/cd/4e2fb7d071e81a9b07719203fd1d329febaded59981b8709663341f758f4/pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d", size = 335784 },
{ url = "https://files.pythonhosted.org/packages/0f/e5/9fc684c59b6f3425cf597d9489c24c47dc96d391be9eb8c9a3c543cd7646/pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332", size = 362215 },
{ url = "https://files.pythonhosted.org/packages/5a/ba/4dbb1ae42775010249ba29d01829353a9b59d9c3caf97df14d548a3b7d4c/pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde", size = 448632 },
{ url = "https://files.pythonhosted.org/packages/10/a9/0932bd7cd677bee8bdc9cb898448e47ada0f74e41f434f4ff687d03a3ea9/pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f", size = 384881 },
{ url = "https://files.pythonhosted.org/packages/31/a9/8c9887ce8bfb8ab0db068ac2f1fe679b713f728c116bd136301c303893cd/pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2", size = 559554 },
{ url = "https://files.pythonhosted.org/packages/f4/7e/70596b098b97799c78e3fc2f89394decca6f5443cac28c54082daf2d48eb/pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a", size = 558246 },
{ url = "https://files.pythonhosted.org/packages/67/5e/e646afbd1632bfbacdae79289d7d5879efdeeb5f5e58327bc5c698731107/pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79", size = 293456 },
{ url = "https://files.pythonhosted.org/packages/7b/f0/d60be6058657bf71281eeaa12bee85e87bac18acf6dbb7b5197bb8416537/pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508", size = 362283 },
{ url = "https://files.pythonhosted.org/packages/68/e5/0f9d8351242ddb119a40b41c0cf1d0c74cc243829eea6811f753a8ecf15f/pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec", size = 352957 },
{ url = "https://files.pythonhosted.org/packages/30/43/70d0a08e5d6ca434ba139d19ec2a4847b0a3e461fbb82e680a9b6a4237ef/pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00", size = 335784 },
{ url = "https://files.pythonhosted.org/packages/fc/a3/7d4c0b3f57bf7b543da9088a78a6bd6c786808ca4098bd5db649fdf9f6a2/pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca", size = 362217 },
{ url = "https://files.pythonhosted.org/packages/8b/03/8c451d569e7f4d9898f155e793f46970eed256c5ae353ecb355584890d8a/pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4", size = 448630 },
{ url = "https://files.pythonhosted.org/packages/84/3a/5e36479e199a034adcf6a1a95c691f0a2781ea55b9ac3bcb887e2f97d82b/pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01", size = 384882 },
{ url = "https://files.pythonhosted.org/packages/4c/25/beff911dda686e0cf169bc3dbe5d10416b376a6dde94eb1bf04aa4035409/pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05", size = 559556 },
{ url = "https://files.pythonhosted.org/packages/e9/e8/f2aaa470adb6c720645f9f9ef30d5b223407ee327e12c6127eccf4218cb8/pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e", size = 558249 },
{ url = "https://files.pythonhosted.org/packages/60/19/c13307ea8504d2c02c63c9dffdae1cefbd068b636ec7b18ccf2ec064d246/pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4", size = 293463 },
{ url = "https://files.pythonhosted.org/packages/6b/36/252d48610295c11c0f18e791dcc133d38c545b0bd19a5c3981652a9acb3c/pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83", size = 288057 },
{ url = "https://files.pythonhosted.org/packages/1e/37/17c8f0e7481a32f21b9002dd68912a8813f2c1d77b984e00af56eb9ae31b/pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7", size = 362284 },
{ url = "https://files.pythonhosted.org/packages/12/e6/08f462f6ea87e2159f19b43ff88231d26e02bda31c10bcb29290a617ace4/pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc", size = 352964 },
{ url = "https://files.pythonhosted.org/packages/47/29/b6877f6b53b91356c2c56d19ddab17b165ca994ad1e57b32c089e79f3fb5/pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37", size = 335848 },
{ url = "https://files.pythonhosted.org/packages/2b/77/62ca666f30b2558342deadda26290a575459a7b59248ea1e978b84175227/pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319", size = 362215 },
{ url = "https://files.pythonhosted.org/packages/e0/29/ce37593f5ea51862c60dadf4e863d604f954478b3abbcc60a14dc05e242c/pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5", size = 448673 },
{ url = "https://files.pythonhosted.org/packages/72/6a/68a8c7b8f1977d89aabfd0e2becb0921e5515dfb365097e98a522334a151/pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f", size = 384891 },
{ url = "https://files.pythonhosted.org/packages/30/e6/edd699300f47a3c53c0d8ed26e905b9a31057c3646211e58cc540716a440/pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518", size = 559558 },
{ url = "https://files.pythonhosted.org/packages/d4/97/95a44aa5e1763d3a966551ed0e12f56508d8dfcc60e1f0395909b6a08626/pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9", size = 558240 },
{ url = "https://files.pythonhosted.org/packages/9a/91/fcd992eb36b77ab43f2cf44307b72c01a6fbb27f55c1bb2d4af30e9a6cb7/pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5", size = 293456 },
{ url = "https://files.pythonhosted.org/packages/3b/60/ba8aa296ca6d76603d58146b4a222cd99e7da33831158b8c00240a896a56/pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f", size = 288054 },
{ url = "https://files.pythonhosted.org/packages/9e/76/65e2e5c8fc7443c13cb50e87c52ab177a2660bd1f978b87de6962b309e07/pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b", size = 362597 },
{ url = "https://files.pythonhosted.org/packages/61/8d/d922528d8a5ec22e1247773a66467eb971a20194d9aa7740c3449f61094a/pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34", size = 353380 },
{ url = "https://files.pythonhosted.org/packages/ee/45/a3657c80bc86c31817dbe1646ef9a170aa2c5a5b1f55ff15ec82557f9509/pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c", size = 336154 },
{ url = "https://files.pythonhosted.org/packages/f0/7e/0b4d36b0210261b6c709640a6dd67dd9c720c3a5c3d8041d6d05f568ba2b/pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10", size = 362743 },
{ url = "https://files.pythonhosted.org/packages/67/b7/b31a9b353b63d131bddceb2fa3477fd1c6bd322793d91f7f0c7607f91c94/pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120", size = 448992 },
{ url = "https://files.pythonhosted.org/packages/3b/e0/8d31270e49e85b28bc035c1d6c418b5842d2f63dcbbc32c8043be5fc1e16/pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6", size = 385164 },
{ url = "https://files.pythonhosted.org/packages/7f/a2/4959088e14ef065f9c3700d3cb202b3aec0fc130f0b661d92c1465cb5a51/pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162", size = 559786 },
{ url = "https://files.pythonhosted.org/packages/bb/7f/93b13e1d98e654cf13c885546337e6b487c9c1b41de384fd05b21272c851/pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16", size = 558443 },
{ url = "https://files.pythonhosted.org/packages/fa/9a/9c96f12c48d5e9e6056358907d691fd35f3f47a468185762b340721fa690/pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027", size = 293520 },
{ url = "https://files.pythonhosted.org/packages/b3/05/49db61d1d0a951526575d36cd571ce389f9c08b7625579e28a0ada5ed842/pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694", size = 362545 },
{ url = "https://files.pythonhosted.org/packages/52/e7/783425867db5df0a9661c2e91d1bd052a0636aee65634e9d758e7b53527e/pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f", size = 353300 },
{ url = "https://files.pythonhosted.org/packages/2a/75/15411992749dd450bb365ae6cc0173480a1411b80cc0a9fdc7d548d254ce/pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac", size = 336118 },
{ url = "https://files.pythonhosted.org/packages/bf/64/14f8cc3147c8ee8339ca37058259134c38092829f85076aa14b5437bf546/pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3", size = 362600 },
{ url = "https://files.pythonhosted.org/packages/bc/c9/d7d20ffa63b0d154f59536dcd2c6361afebc6e44a76ca34131d492624299/pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512", size = 449101 },
{ url = "https://files.pythonhosted.org/packages/22/aa/2d6846d7f382262d894902d3cf8ee66b02aee3bab2910db0004ca0f9ef18/pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56", size = 385208 },
{ url = "https://files.pythonhosted.org/packages/89/1c/ad9726d5e1d85c5ba24f9021baf5f6f39ef18e94fa851a7c9231adca9e75/pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d", size = 559851 },
{ url = "https://files.pythonhosted.org/packages/b6/1c/e13764e578f646a1b50faad8045bb05a755e5a913854c89a0e7dd4caaa19/pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc", size = 558520 },
{ url = "https://files.pythonhosted.org/packages/4e/4f/6c8569ba60b933c726f6c0051519167d9f9167e49d03c6074b57bb4c204a/pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae", size = 293746 },
{ url = "https://files.pythonhosted.org/packages/0f/7f/24d8c167937d663a9cf6d5fc5e87a87bfa320c3f002d4fbbc7bd5ff3b6f8/pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1", size = 362388 },
{ url = "https://files.pythonhosted.org/packages/55/e1/33775ee68f8bbb0da967dfd818706ee69e0a054f663ee6111d5c7639f67a/pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07", size = 353062 },
{ url = "https://files.pythonhosted.org/packages/3e/1b/c3e399148c0d69c2c84c2eda45cd3580990b13f36d0c96516591bf4def56/pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a", size = 335871 },
{ url = "https://files.pythonhosted.org/packages/32/6b/23dde8bd3fb78f693b81bd8fc67769b2a461918d51ed6ddf486a1a97e199/pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4", size = 384859 },
{ url = "https://files.pythonhosted.org/packages/1d/1b/a3e0387f586d6121a15e6d02f7ae8cc3cd1ebb136fd243c1c191136ed518/pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04", size = 559441 },
{ url = "https://files.pythonhosted.org/packages/d7/23/91dea81265d5d11af0cd5053ca76730cc2c5ac14085c9a923d448e74c67f/pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7", size = 558189 },
{ url = "https://files.pythonhosted.org/packages/7a/8a/166625d30f927e800e99f3f6556d8b3f4ad952c62d6a774844d73542b84b/pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820", size = 293657 },
{ url = "https://files.pythonhosted.org/packages/c9/ff/83ce5b98c29d86d6afb951f9b73f0a1e4248fd74ea0f9104e3814d0fa479/pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795", size = 362442 },
{ url = "https://files.pythonhosted.org/packages/b3/c5/464ec6a334102bbcd4428d13ead2180e87b56da27a782a2ba2c15376120d/pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b", size = 353100 },
{ url = "https://files.pythonhosted.org/packages/9b/5a/4fb3a04cab1bf1c304c2d00d7d0f063e09776ac58a9a9c341a77e097ae08/pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0", size = 335919 },
{ url = "https://files.pythonhosted.org/packages/7b/a9/752c29e4ae27aa2e51940861a8c803e0966d16b5cca65229ef106426059b/pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a", size = 384928 },
{ url = "https://files.pythonhosted.org/packages/12/23/2816c02e5b3ce4006c186353a859ba2f7834e7ffc8ab61c95d71dfd10a91/pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8", size = 559525 },
{ url = "https://files.pythonhosted.org/packages/b2/73/0b740805f94e7fa0915d7ae00d51a3676ca4c10401aa43ca611778075b4d/pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c", size = 558323 },
{ url = "https://files.pythonhosted.org/packages/d0/b0/73581fe1755213a5a33b9f91c72a4d0705bbe3e7f19413b0af4878c73d70/pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23", size = 293471 },
{ url = "https://files.pythonhosted.org/packages/ac/1c/69adbf18071d9c5571bed60aa881d76380d5121a7adc8c765375def08506/pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157", size = 362376 },
{ url = "https://files.pythonhosted.org/packages/a7/18/2c0d556f1a6832fa4c5c1d466ec179087d250e654f6fa8c5723f6377c7d8/pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0", size = 353041 },
{ url = "https://files.pythonhosted.org/packages/02/a6/951ff1930b796b272c9a372f0307c9e7f6b3ef9267972f404ee16bf32fd2/pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88", size = 335877 },
{ url = "https://files.pythonhosted.org/packages/76/b3/2bb091f05d1e94bc20549c2318d65606f704fb881728cc2f6bf146037443/pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a", size = 384858 },
{ url = "https://files.pythonhosted.org/packages/dc/51/b49eed0f7c23e7fb1a6affc482f6cc6fbf0bb76a2156c792a97646cd513e/pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a", size = 559448 },
{ url = "https://files.pythonhosted.org/packages/80/24/65427759911ec8823e728a40fa86fa8e70f275d0eb036c14c631366f1213/pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d", size = 558185 },
{ url = "https://files.pythonhosted.org/packages/5a/8b/f3ac476c70a39818a56dd24144cc2bee276e7a5fe3d254ba5238769224c8/pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b", size = 293645 },
]
[[package]]
name = "phonenumbers"
version = "8.13.50"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/55/bf/6d62a014a43e1e485185b9652ef309f8ce8998f65c9a1b7d4b89c46cb76b/phonenumbers-8.13.50.tar.gz", hash = "sha256:e05ac6fb7b98c6d719a87ea895b9fc153673b4a51f455ec9afaf557ef4629da6", size = 2297710 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/d4/2011babd77b9709dd80f89aa74611fdace859e0571cd9e79ba3f95902441/phonenumbers-8.13.50-py2.py3-none-any.whl", hash = "sha256:bb95dbc0d9979c51f7ad94bcd780784938958861fbb4b75a2fe39ccd3d58954a", size = 2583092 },
]
[[package]]
name = "pluggy"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
]
[[package]]
name = "pycountry"
version = "24.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-resources", marker = "python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/57/c389fa68c50590881a75b7883eeb3dc15e9e73a0fdc001cdd45c13290c92/pycountry-24.6.1.tar.gz", hash = "sha256:b61b3faccea67f87d10c1f2b0fc0be714409e8fcdcc1315613174f6466c10221", size = 6043910 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/ec/1fb891d8a2660716aadb2143235481d15ed1cbfe3ad669194690b0604492/pycountry-24.6.1-py3-none-any.whl", hash = "sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f", size = 6335189 },
]
[[package]]
name = "pydantic"
version = "2.9.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
]
[[package]]
name = "pydantic-core"
version = "2.23.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 },
{ url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 },
{ url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 },
{ url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 },
{ url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 },
{ url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 },
{ url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 },
{ url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 },
{ url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 },
{ url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 },
{ url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 },
{ url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 },
{ url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 },
{ url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 },
{ url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
{ url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 },
{ url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 },
{ url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 },
{ url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 },
{ url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 },
{ url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 },
{ url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 },
{ url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 },
{ url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 },
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
{ url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
{ url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
{ url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
{ url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
{ url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
{ url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
{ url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
{ url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
{ url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
{ url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
{ url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
{ url = "https://files.pythonhosted.org/packages/b0/3f/790c72662823955c4803487bcda8dfce5a9e2ddcaf709f7993e12ede29fd/pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555", size = 1867828 },
{ url = "https://files.pythonhosted.org/packages/89/f6/6ceeeb0005fabdf3f99a7fa1be52db41c328b438ebfa88f7b3f2815fb157/pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658", size = 1748962 },
{ url = "https://files.pythonhosted.org/packages/13/08/c1c49218a87c6e079730fed0357a5883c2f6fbf43e9da0eb2501384e0975/pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271", size = 1800821 },
{ url = "https://files.pythonhosted.org/packages/0a/92/e8f4de3453a3446ca2da5dc5d636fbd644dc9923f74812cfb63549df79ee/pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665", size = 1805527 },
{ url = "https://files.pythonhosted.org/packages/3e/06/cb8891f40d4b1d5744c24a3082e3031b16727fdc927cf4dc6bbe2fb9e9a3/pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368", size = 2002267 },
{ url = "https://files.pythonhosted.org/packages/a3/3b/7d2ab72afa3deb18baeb6c3373ad3181922da541bfae63aecbef51a9fadc/pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13", size = 2654837 },
{ url = "https://files.pythonhosted.org/packages/a5/a4/229432a701acd98575b88ccaff82e8435c75c44cb30522004afb263e3756/pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad", size = 2054177 },
{ url = "https://files.pythonhosted.org/packages/50/bd/4a40a83e476af0a86e1f57559dd324b922b5e60faaa866de400efe033ca1/pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12", size = 1920918 },
{ url = "https://files.pythonhosted.org/packages/4b/98/857e2bdae80432efff030812add720d6797b63c790f13e123c8274a3337b/pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2", size = 1965814 },
{ url = "https://files.pythonhosted.org/packages/ab/87/cacb1152a821340e11f870e0fbd6613c2f05278dab5def47c7818eac1a22/pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb", size = 2111058 },
{ url = "https://files.pythonhosted.org/packages/5c/ce/a3bd2de2582be6e5b187218896cac734377fcc8d5a54f98b83d710125ef0/pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6", size = 1734749 },
{ url = "https://files.pythonhosted.org/packages/db/c8/e6ed2172bb2594a256c7756a70109fa31ab80d1cebb9003ad6c54e10c0d1/pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556", size = 1920302 },
{ url = "https://files.pythonhosted.org/packages/7a/04/2580b2deaae37b3e30fc30c54298be938b973990b23612d6b61c7bdd01c7/pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a", size = 1868200 },
{ url = "https://files.pythonhosted.org/packages/39/6e/e311bd0751505350f0cdcee3077841eb1f9253c5a1ddbad048cd9fbf7c6e/pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36", size = 1749316 },
{ url = "https://files.pythonhosted.org/packages/d0/b4/95b5eb47c6dc8692508c3ca04a1f8d6f0884c9dacb34cf3357595cbe73be/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b", size = 1800880 },
{ url = "https://files.pythonhosted.org/packages/da/79/41c4f817acd7f42d94cd1e16526c062a7b089f66faed4bd30852314d9a66/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323", size = 1807077 },
{ url = "https://files.pythonhosted.org/packages/fb/53/d13d1eb0a97d5c06cf7a225935d471e9c241afd389a333f40c703f214973/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3", size = 2002859 },
{ url = "https://files.pythonhosted.org/packages/53/7d/6b8a1eff453774b46cac8c849e99455b27167971a003212f668e94bc4c9c/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df", size = 2661437 },
{ url = "https://files.pythonhosted.org/packages/6c/ea/8820f57f0b46e6148ee42d8216b15e8fe3b360944284bbc705bf34fac888/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c", size = 2054404 },
{ url = "https://files.pythonhosted.org/packages/0f/36/d4ae869e473c3c7868e1cd1e2a1b9e13bce5cd1a7d287f6ac755a0b1575e/pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55", size = 1921680 },
{ url = "https://files.pythonhosted.org/packages/0d/f8/eed5c65b80c4ac4494117e2101973b45fc655774ef647d17dde40a70f7d2/pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040", size = 1966093 },
{ url = "https://files.pythonhosted.org/packages/e8/c8/1d42ce51d65e571ab53d466cae83434325a126811df7ce4861d9d97bee4b/pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605", size = 2111437 },
{ url = "https://files.pythonhosted.org/packages/aa/c9/7fea9d13383c2ec6865919e09cffe44ab77e911eb281b53a4deaafd4c8e8/pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6", size = 1735049 },
{ url = "https://files.pythonhosted.org/packages/98/95/dd7045c4caa2b73d0bf3b989d66b23cfbb7a0ef14ce99db15677a000a953/pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29", size = 1920180 },
{ url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 },
{ url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 },
{ url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 },
{ url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 },
{ url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 },
{ url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 },
{ url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 },
{ url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 },
{ url = "https://files.pythonhosted.org/packages/32/fd/ac9cdfaaa7cf2d32590b807d900612b39acb25e5527c3c7e482f0553025b/pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21", size = 1857850 },
{ url = "https://files.pythonhosted.org/packages/08/fe/038f4b2bcae325ea643c8ad353191187a4c92a9c3b913b139289a6f2ef04/pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb", size = 1740265 },
{ url = "https://files.pythonhosted.org/packages/51/14/b215c9c3cbd1edaaea23014d4b3304260823f712d3fdee52549b19b25d62/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59", size = 1793912 },
{ url = "https://files.pythonhosted.org/packages/62/de/2c3ad79b63ba564878cbce325be725929ba50089cd5156f89ea5155cb9b3/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577", size = 1942870 },
{ url = "https://files.pythonhosted.org/packages/cb/55/c222af19e4644c741b3f3fe4fd8bbb6b4cdca87d8a49258b61cf7826b19e/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744", size = 1915610 },
{ url = "https://files.pythonhosted.org/packages/c4/7a/9a8760692a6f76bb54bcd43f245ff3d8b603db695899bbc624099c00af80/pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef", size = 1958403 },
{ url = "https://files.pythonhosted.org/packages/4c/91/9b03166feb914bb5698e2f6499e07c2617e2eebf69f9374d0358d7eb2009/pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8", size = 2101154 },
{ url = "https://files.pythonhosted.org/packages/1d/d9/1d7ecb98318da4cb96986daaf0e20d66f1651d0aeb9e2d4435b916ce031d/pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e", size = 1920855 },
]
[[package]]
name = "pydantic-extra-types"
version = "2.10.1"
source = { editable = "." }
dependencies = [
{ name = "pydantic" },
{ name = "typing-extensions" },
]
[package.optional-dependencies]
all = [
{ name = "pendulum" },
{ name = "phonenumbers" },
{ name = "pycountry" },
{ name = "python-ulid" },
{ name = "pytz" },
{ name = "semver" },
{ name = "tzdata" },
]
pendulum = [
{ name = "pendulum" },
]
phonenumbers = [
{ name = "phonenumbers" },
]
pycountry = [
{ name = "pycountry" },
]
python-ulid = [
{ name = "python-ulid" },
]
semver = [
{ name = "semver" },
]
[package.dev-dependencies]
dev = [
{ name = "coverage", extra = ["toml"] },
{ name = "dirty-equals" },
{ name = "pytest" },
{ name = "pytest-pretty" },
]
extra = [
{ name = "annotated-types" },
{ name = "coverage", extra = ["toml"] },
{ name = "dirty-equals" },
{ name = "mypy" },
{ name = "pytest" },
{ name = "pytest-pretty" },
{ name = "ruff" },
{ name = "types-pytz" },
]
lint = [
{ name = "annotated-types" },
{ name = "mypy" },
{ name = "ruff" },
{ name = "types-pytz" },
]
[package.metadata]
requires-dist = [
{ name = "pendulum", marker = "extra == 'all'", specifier = ">=3.0.0,<4.0.0" },
{ name = "pendulum", marker = "extra == 'pendulum'", specifier = ">=3.0.0,<4.0.0" },
{ name = "phonenumbers", marker = "extra == 'all'", specifier = ">=8,<9" },
{ name = "phonenumbers", marker = "extra == 'phonenumbers'", specifier = ">=8,<9" },
{ name = "pycountry", marker = "extra == 'all'", specifier = ">=23" },
{ name = "pycountry", marker = "extra == 'pycountry'", specifier = ">=23" },
{ name = "pydantic", specifier = ">=2.5.2" },
{ name = "python-ulid", marker = "python_full_version >= '3.9' and extra == 'all'", specifier = ">=1,<4" },
{ name = "python-ulid", marker = "python_full_version >= '3.9' and extra == 'python-ulid'", specifier = ">=1,<4" },
{ name = "python-ulid", marker = "python_full_version < '3.9' and extra == 'all'", specifier = ">=1,<2" },
{ name = "python-ulid", marker = "python_full_version < '3.9' and extra == 'python-ulid'", specifier = ">=1,<2" },
{ name = "pytz", marker = "extra == 'all'", specifier = ">=2024.1" },
{ name = "semver", marker = "extra == 'all'", specifier = "~=3.0.2" },
{ name = "semver", marker = "extra == 'all'", specifier = ">=3.0.2" },
{ name = "semver", marker = "extra == 'semver'", specifier = ">=3.0.2" },
{ name = "typing-extensions" },
{ name = "tzdata", marker = "extra == 'all'", specifier = ">=2024.1" },
]
[package.metadata.requires-dev]
dev = [
{ name = "coverage", extras = ["toml"], specifier = ">=7.6.1" },
{ name = "dirty-equals", specifier = ">=0.7.1" },
{ name = "pytest", specifier = ">=8.3.2" },
{ name = "pytest-pretty", specifier = ">=1.2.0" },
]
extra = [
{ name = "annotated-types", specifier = ">=0.7.0" },
{ name = "coverage", extras = ["toml"], specifier = ">=7.6.1" },
{ name = "dirty-equals", specifier = ">=0.7.1" },
{ name = "mypy", specifier = ">=0.910" },
{ name = "pytest", specifier = ">=8.3.2" },
{ name = "pytest-pretty", specifier = ">=1.2.0" },
{ name = "ruff", specifier = ">=0.7.4" },
{ name = "types-pytz", specifier = ">=2024.1.0.20240417" },
]
lint = [
{ name = "annotated-types", specifier = ">=0.7.0" },
{ name = "mypy", specifier = ">=0.910" },
{ name = "ruff", specifier = ">=0.7.4" },
{ name = "types-pytz", specifier = ">=2024.1.0.20240417" },
]
[[package]]
name = "pygments"
version = "2.18.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
]
[[package]]
name = "pytest"
version = "8.3.3"
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 = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 },
]
[[package]]
name = "pytest-pretty"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/18/30ad0408295f3157f7a4913f0eaa51a0a377ebad0ffa51ff239e833c6c72/pytest_pretty-1.2.0.tar.gz", hash = "sha256:105a355f128e392860ad2c478ae173ff96d2f03044692f9818ff3d49205d3a60", size = 6542 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/fe/d44d391312c1b8abee2af58ee70fabb1c00b6577ac4e0bdf25b70c1caffb/pytest_pretty-1.2.0-py3-none-any.whl", hash = "sha256:6f79122bf53864ae2951b6c9e94d7a06a87ef753476acd4588aeac018f062036", size = 6180 },
]
[[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 }
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 },
]
[[package]]
name = "python-ulid"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/8b/0580d8ee0a73a3f3869488856737c429cbaa08b63c3506275f383c4771a8/python-ulid-1.1.0.tar.gz", hash = "sha256:5fb5e4a91db8ca93e8938a613360b3def299b60d41f847279a8c39c9b2e9c65e", size = 19992 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/8e/c30b08ee9b8dc9b4a10e782c2a7fd5de55388201ddebfe0f7ab99dfbb349/python_ulid-1.1.0-py3-none-any.whl", hash = "sha256:88c952f6be133dbede19c907d72d26717d2691ec8421512b573144794d891e24", size = 9360 },
]
[[package]]
name = "pytz"
version = "2024.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
]
[[package]]
name = "rich"
version = "13.9.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
]
[[package]]
name = "ruff"
version = "0.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 },
{ url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 },
{ url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 },
{ url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 },
{ url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 },
{ url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 },
{ url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 },
{ url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 },
{ url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 },
{ url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 },
{ url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 },
{ url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 },
{ url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 },
{ url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 },
{ url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 },
{ url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 },
{ url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 },
]
[[package]]
name = "semver"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/41/6c/a536cc008f38fd83b3c1b98ce19ead13b746b5588c9a0cb9dd9f6ea434bc/semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc", size = 214988 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/77/0cc7a8a3bc7e53d07e8f47f147b92b0960e902b8254859f4aee5c4d7866b/semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4", size = 17099 },
]
[[package]]
name = "six"
version = "1.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
]
[[package]]
name = "time-machine"
version = "2.15.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/82d358c4d53555f031c2343d1c235b56b9f3b0a60ac3adc555778fe87506/time_machine-2.15.0.tar.gz", hash = "sha256:ebd2e63baa117ded04b978813fcd1279d3fc6be2149c9cac75c716b6f1db774c", size = 25067 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/67/47/35413db37da55865fdbf60649bcb948cc2559f420ef4e91e77e4e24c71b8/time_machine-2.15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:892d016789b59950989b2db188dcd46cf16d34e8daf2343e33b679b0c5fd1001", size = 20779 },
{ url = "https://files.pythonhosted.org/packages/e0/c3/fda6d2336737d0331eb55357db1dc916af14c4fda77c69ad8b3733b003c4/time_machine-2.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4428bdae507996aa3fdeb4727bca09e26306fa64a502e7335207252684516cbf", size = 17040 },
{ url = "https://files.pythonhosted.org/packages/36/e1/71200f24d668e5183e875a08ba5e557b6107c1b7d57fa6d54ac24ad10234/time_machine-2.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0302568338c8bd333ed0698231dbb781b70ead1a5579b4ac734b9bf88313229f", size = 34811 },
{ url = "https://files.pythonhosted.org/packages/9e/2f/4b9289ea07978ad5c3469c872c7eeadf5f5b3a1dcebe2fb274c2486fc220/time_machine-2.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18fc4740073e67071472c48355775ec6d1b93af5c675524b7de2474e0dcd8741", size = 32820 },
{ url = "https://files.pythonhosted.org/packages/5f/9e/9f838c91d2248d716281af60dfea4131438c6ad6d7405ebc6e47f8c25c3b/time_machine-2.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:768d33b484a35da93731cc99bdc926b539240a78673216cdc6306833d9072350", size = 34635 },
{ url = "https://files.pythonhosted.org/packages/f3/10/1048b5ba6de55779563f005de5fbfb764727bf9678ad7701cea480b3816c/time_machine-2.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73a8c8160d2a170dadcad5b82fb5ee53236a19cec0996651cf4d21da0a2574d5", size = 34326 },
{ url = "https://files.pythonhosted.org/packages/13/82/6b4df8e5abf754b0ccceeb59fa32486d28c65f67d4ada37ff8b1e9f52006/time_machine-2.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09fd839a321a92aa8183206c383b9725eaf4e0a28a70e4cb87db292b352eeefb", size = 32639 },
{ url = "https://files.pythonhosted.org/packages/cf/07/95e380c46136252401d97f613782a10061b3c11b61edaeb78e83aedc1a88/time_machine-2.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:838a6d117739f1ae6ecc45ec630fa694f41a85c0d07b1f3b1db2a6cc52c1808b", size = 34021 },
{ url = "https://files.pythonhosted.org/packages/b6/0c/6595fa82bd70bc7e8065bfc6534e51a27c18978f7c158d6392c979cace2c/time_machine-2.15.0-cp310-cp310-win32.whl", hash = "sha256:d24d2ec74923b49bce7618e3e7762baa6be74e624d9829d5632321de102bf386", size = 19413 },
{ url = "https://files.pythonhosted.org/packages/2f/3d/cb3c1cecfeb4b6423302ee1b2863617390500f67526f0fc1fb5641db05f6/time_machine-2.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:95c8e7036cf442480d0bf6f5fde371e1eb6dbbf5391d7bdb8db73bd8a732b538", size = 20280 },
{ url = "https://files.pythonhosted.org/packages/22/aa/96aaac88738369fba43d5cb076bb09290b1a2cbd84210bcc0a9a519c7970/time_machine-2.15.0-cp310-cp310-win_arm64.whl", hash = "sha256:660810cd27a8a94cb5e845e8f28a95e70b01ff0c45466d394c4a0cba5a0ae279", size = 18392 },
{ url = "https://files.pythonhosted.org/packages/ce/54/829ab196c3306eb4cee95e3c8e7d004e15877b36479de5d2ecc72fc1d3d4/time_machine-2.15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:674097dd54a0bbd555e7927092c74428c4c07268ad52bca38cfccc3214707e50", size = 20448 },
{ url = "https://files.pythonhosted.org/packages/e1/48/a06f8c7db768db501a60210a48f3d37b7b3d65ca85aa8dc08147eb204b4a/time_machine-2.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e83fd6112808d1d14d1a57397c6fa3bd71bb2f3b8800036e12366e3680819b9", size = 16897 },
{ url = "https://files.pythonhosted.org/packages/e7/f8/73265927e3da54a417536dc3d8c9aad806b62b8133099a7ee12661aba1a3/time_machine-2.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b095a1de40ca1afaeae8df3f45e26b645094a1912e6e6871e725fcf06ecdb74a", size = 32789 },
{ url = "https://files.pythonhosted.org/packages/8a/a4/bcf8ad40a4c6973a67aba5df7ed704dc34256835fb074cfb4d4caa0f93a5/time_machine-2.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4601fe7a6b74c6fd9207e614d9db2a20dd4befd4d314677a0feac13a67189707", size = 30911 },
{ url = "https://files.pythonhosted.org/packages/13/87/a6de1b187f5468781e0e722c877323625227151cc8ffff363a7391b01149/time_machine-2.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245ef73f9927b7d4909d554a6a0284dbc5dee9730adea599e430b37c9e9fa203", size = 32644 },
{ url = "https://files.pythonhosted.org/packages/33/1f/7378d5a032467891a1005e546532223b97c53440c6359b1133696a5cb1ef/time_machine-2.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:704abc7f3403584cca9c01c5809812e0bd70632ea4251389fae4f45e11aad94f", size = 32472 },
{ url = "https://files.pythonhosted.org/packages/68/14/2fab61ad2c9a831925bce3d5e341fa2108ba062c2de0c190569e1ee6a1c9/time_machine-2.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6425001e50a0c82108caed438233066cea04d42a8fc9c49bfcf081a5b96e5b4e", size = 30882 },
{ url = "https://files.pythonhosted.org/packages/8a/01/f5146b9956b548072000a87f3eb8dbb2591ada1516a5d889aa12b373948f/time_machine-2.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5d4073b754f90b19f28d036ec5143d3fca3a75e4d4241d78790a6178b00bb373", size = 32176 },
{ url = "https://files.pythonhosted.org/packages/ca/09/8a8488e6d3faf3cb68d078f27ca94aa3ba1bc08d5f804265c590208a70f5/time_machine-2.15.0-cp311-cp311-win32.whl", hash = "sha256:8817b0f7d7830215261b18db83c9c3ef1da6bb64da5c292d7c70b9a46e5a6745", size = 19305 },
{ url = "https://files.pythonhosted.org/packages/75/33/d8411b197a08eedb3ce086022cdf4faf1f15738607a2d943fd5286f57fdd/time_machine-2.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:ddad27a62df2ea47b7b483009fbfcf167a71d702cbd8e2eefd9ddc1c93146658", size = 20210 },
{ url = "https://files.pythonhosted.org/packages/8c/f5/e9b5d7be612403e570a42af5c2823506877e726f77f2d6ff272d72d0aed3/time_machine-2.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f021aa2dbd8fbfe54d3fa2258518129108b7496922b3bcff2cf5991078eec67", size = 18278 },
{ url = "https://files.pythonhosted.org/packages/49/47/46bf332f4ecd7f35e197131b9c23daa39423cf71b814e36e9d5df3cf2380/time_machine-2.15.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a22f47c34ee1fcf7d93a8c5c93135499aac879d9d5d8f820bd28571a30fdabcd", size = 20436 },
{ url = "https://files.pythonhosted.org/packages/f1/36/9990f16868ffdefe6b5aecfdfbcb11718230e414ca61a887fbee884f70e5/time_machine-2.15.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b684f8ecdeacd6baabc17b15ac1b054ca62029193e6c5367ef00b3516671de80", size = 16926 },
{ url = "https://files.pythonhosted.org/packages/ca/2d/007955a0899cd079a400bc204c03edc76274de2471d94ca235ff587a6eba/time_machine-2.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f7add997684bc6141e1c80f6ba0c38ffe316ba277a4074e61b1b7b4f5a172bf", size = 17157 },
{ url = "https://files.pythonhosted.org/packages/7a/e2/66d26450f9bfd1b019abdefbf0c62e760efc8992c7bf88d6c18f7ea6b94a/time_machine-2.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31af56399bf7c9ef76a3f7b6d9471dffa8f06ee373c194a374b69523f9061de9", size = 34005 },
{ url = "https://files.pythonhosted.org/packages/46/2c/dc2c42200aee6b47a55274d984736f7507ecfbfd0345114ec511ec444bef/time_machine-2.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b94cba3edfc54bcb3ab5be616a2f50fa48be438e5af970824efdf882d1bc31", size = 31926 },
{ url = "https://files.pythonhosted.org/packages/87/59/10d8faecbd233b0da831eb9973c3e650c83fb3d443edb607b6b26c9688ac/time_machine-2.15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3862dda89bdb05f9d521b08fdcb24b19a7dd9f559ae324f4301ba7a07b6eea64", size = 33685 },
{ url = "https://files.pythonhosted.org/packages/2e/a4/702ad9e328cbc7b3f1833dee4886d0994e52bc2d9640effa64bccc7740fa/time_machine-2.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e1790481a6b9ce38888f22ce30710244067898c3ac4805a0e061e381f3db3506", size = 33609 },
{ url = "https://files.pythonhosted.org/packages/a0/9d/6009d28ad395a45b5bb91af31616494b4e61d6d9df252d0e5933cd3aa8f1/time_machine-2.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a731c03bc00552ee6cc685a59616d36003124e7e04c6ddf65c2c47f1c3d85480", size = 31737 },
{ url = "https://files.pythonhosted.org/packages/36/22/b55df08cf48d46af93ee2f4310dd88c8519bc5f98afd24af57a81a5d5272/time_machine-2.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e6776840aea3ff5ab6924b50117957da62db51b109b3b491c0d5817a804b1a8e", size = 33253 },
{ url = "https://files.pythonhosted.org/packages/52/d7/bb5e92f0b0268cd13baad874d82b0e964a847cf52740464abeec48dc1642/time_machine-2.15.0-cp312-cp312-win32.whl", hash = "sha256:9479530e3fce65f6149058071fa4df8150025f15b43b103445f619842981a87c", size = 19369 },
{ url = "https://files.pythonhosted.org/packages/f4/33/276537ba292fc7ee67e6aef7566b2a6b313dbc4d479e5e80eed43094ef48/time_machine-2.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f3ab4185c1f72010846ca9fccb08349e23a2b52982a18d9870e848ce9f1c86", size = 20219 },
{ url = "https://files.pythonhosted.org/packages/e0/0e/e8b75032248f59a2bc5c125d3a41242b1e577caa07585c42b22373d6466d/time_machine-2.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:c0473dfa8f17c6a9a250b2bd6a5b62af3aa7d22518f701649115f1085d5e35ab", size = 18297 },
{ url = "https://files.pythonhosted.org/packages/7c/a1/ebe212530628aa29a86a771ca77cb2d1ead667382cfa89a3fb849e3f0108/time_machine-2.15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f50f10058b884d45cd8a50423bf561b1f9f9df7058abeb8b318700c8bcf4bb54", size = 20492 },
{ url = "https://files.pythonhosted.org/packages/29/0d/2a19951729e50d8809e161e533585c0be5ae39c0cf40140877353847b9b5/time_machine-2.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:df6f618b98f0848fd8d07039541e10f23db679d8283f8719e870a98e1ef8e639", size = 16961 },
{ url = "https://files.pythonhosted.org/packages/85/eb/33cf2173758b128f55c880c492e17b70f6c325e7bee879f9b0171cfe02a0/time_machine-2.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52468a0784544eba708c0ae6bc5e8c5dcfd685495a60f7f74028662c984bd9cd", size = 34051 },
{ url = "https://files.pythonhosted.org/packages/e1/23/da9a7935a7be952ab6163caf976b6bad049f6e117f3a396ecc381b077cb6/time_machine-2.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08800c28160f4d32ca510128b4e201a43c813e7a2dd53178fa79ebe050eba13", size = 31966 },
{ url = "https://files.pythonhosted.org/packages/0b/0d/a8e3cbd91ffa98b0fa50b6e29d03151f37aa04cca4dd658e33cdf2b4731e/time_machine-2.15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d395211736d9844537a530287a7c64b9fda1d353e899a0e1723986a0859154", size = 33727 },
{ url = "https://files.pythonhosted.org/packages/07/53/c084031980706517cfbae9f462e455d61c7cbf9b66a8a83bcc5b79d00836/time_machine-2.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b177d334a35bf2ce103bfe4e0e416e4ee824dd33386ea73fa7491c17cc61897", size = 33690 },
{ url = "https://files.pythonhosted.org/packages/a0/30/5c87e8709ba00c893faf8a9bddf06abf317fdc6103fe78bdf99c53ab444f/time_machine-2.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9a6a9342fae113b12aab42c790880c549d9ba695b8deff27ee08096eedd67569", size = 31809 },
{ url = "https://files.pythonhosted.org/packages/04/7b/92ac7c556cd123bf8b23dbae3cf4a273c276110b87d0c4b5600c2cec8e70/time_machine-2.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bcbb25029ee8756f10c6473cea5ef21707a1d9a8752cdf29fad3a5f34aa4a313", size = 33325 },
{ url = "https://files.pythonhosted.org/packages/e9/71/36c74bab3d4e4385d31610b367da1535a36d17358df058e0920a7510e17c/time_machine-2.15.0-cp313-cp313-win32.whl", hash = "sha256:29b988b1f09f2a083b12b6b054787b799ae91ee15bb0e9de3e48f880e4d68674", size = 19397 },
{ url = "https://files.pythonhosted.org/packages/a4/69/ea5976c43a673894f2fa85a05b28a610b474472f393e59722a6946f7070b/time_machine-2.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:d828721dcbcb94b904a6b25df67c2513ecd24cd9e36694f38b9f0fa71c7c6103", size = 20242 },
{ url = "https://files.pythonhosted.org/packages/cc/c8/26367d0b8dfaf7445576fe0051bff61b8f5be752e7bf3e8807ed7fa3a343/time_machine-2.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:008bd668d933b1a029c81805bcdc0132390c2545b103cf8e6709e3adbc37989d", size = 18337 },
{ url = "https://files.pythonhosted.org/packages/97/54/eeac8568cad4f3eb255cc78f1fa2c36147afd3fcba770283bf2b2a188b33/time_machine-2.15.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e99689f6c6b9ca6e2fc7a75d140e38c5a7985dab61fe1f4e506268f7e9844e05", size = 20674 },
{ url = "https://files.pythonhosted.org/packages/5c/82/488341de4c03c0856aaf5db74f2a8fe18dcc7657401334c54c4aa6cb0fc6/time_machine-2.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:671e88a6209a1cf415dc0f8c67d2b2d3b55b436cc63801a518f9800ebd752959", size = 16990 },
{ url = "https://files.pythonhosted.org/packages/9c/cc/0ca559e71be4eb05917d02364f4d356351b31dd0d6ff3c4c86fa4de0a03e/time_machine-2.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b2d28daf4cabc698aafb12135525d87dc1f2f893cbd29a8a6fe0d8d36d1342c", size = 35501 },
{ url = "https://files.pythonhosted.org/packages/92/a0/14905a5feecc6d2e87ebe6dd2b044358422836ed173071cdc1245aa5ec88/time_machine-2.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cd9f057457d12604be18b623bcd5ae7d0b917ad66cb510ee1135d5f123666e2", size = 33430 },
{ url = "https://files.pythonhosted.org/packages/19/a4/282b65b4d835dfd7b863777cc4206bec375285bda884dc22bd1264716f6a/time_machine-2.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97dc6793e512a62ba9eab250134a2e67372c16ae9948e73d27c2ef355356e2e1", size = 35366 },
{ url = "https://files.pythonhosted.org/packages/93/8e/f7db3f641f1ff86b98594c9cf8d71c8d292cc2bde06c1369ce4745494cc5/time_machine-2.15.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0630a32e9ebcf2fac3704365b31e271fef6eabd6fedfa404cd8dbd244f7fc84d", size = 34373 },
{ url = "https://files.pythonhosted.org/packages/3d/9f/8c8ac57ccb29e692e0940e58515a9afb844d2d11b7f057a0fe153bfe4877/time_machine-2.15.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:617c9a92d8d8f60d5ef39e76596620503752a09f834a218e5b83be352fdd6c91", size = 32667 },
{ url = "https://files.pythonhosted.org/packages/ec/e7/f0c6f9507b0bbfdec54d256b6efc9417ae1a01ce6320c2a42235b807cf86/time_machine-2.15.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3f7eadd820e792de33a9ec91f8178a2b9088e4e8b9a166953419ddc4ec5f7cfe", size = 34070 },
{ url = "https://files.pythonhosted.org/packages/20/82/ac2d8343db8dade1372457d7a5694f069882d9eac110ddce2643ef0501aa/time_machine-2.15.0-cp38-cp38-win32.whl", hash = "sha256:b7b647684eb2e1fd1e5e6b101249d5fe9d6117c117b5e336ad8dd75af48d2d1f", size = 19396 },
{ url = "https://files.pythonhosted.org/packages/3a/12/ac7bb1e932536fce359e021a62c2a5a30c4e470293d6f8b2fb47077562dc/time_machine-2.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b48abd7745caec1a78a16a048966cde14ff6ccb04d471a7201532648d3f77d14", size = 20266 },
{ url = "https://files.pythonhosted.org/packages/ec/bf/d9689e1fa669e575c3ed57bf4f9205a9b5fbe703dc7ef89ba5ce9aa39a38/time_machine-2.15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c2b1c91b437133c672e374857eccb1dd2c2d9f8477ae3b35138382d5ef19846", size = 20775 },
{ url = "https://files.pythonhosted.org/packages/d8/4b/4314a7882b470c52cd527601107b1163e19d37fb1eb31eea0f8d73d0b178/time_machine-2.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:79bf1ef6850182e09d86e61fa31717da56014a3b2234afb025fca1f2a43ac07b", size = 17037 },
{ url = "https://files.pythonhosted.org/packages/f1/b8/adf2f8b8e10f6f5e498b0cddd103f6520144af53fb27b5a01eca50812a92/time_machine-2.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:658ea8477fa020f08435fb7277635eb0b50cd5206b9d4cbe10e9a5466b01f855", size = 34511 },
{ url = "https://files.pythonhosted.org/packages/e3/86/fda41a9e8115fd377f2d4d15c91a414f75cb8f2cd7f8bde974855a0f381f/time_machine-2.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c947135750d20f35acac290c34f1acf5771fc166a3fbc0e3816a97c756aaa5f5", size = 32533 },
{ url = "https://files.pythonhosted.org/packages/cb/7e/1e2e69fee659f00715f12392cabea1920245504862eab2caac6e3f30de5b/time_machine-2.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dee3a0dd1866988c49a5d00564404db9bcdf49ca92f9c4e8b6c99609d64e698", size = 34348 },
{ url = "https://files.pythonhosted.org/packages/41/da/8db2df73ebe9f23af25b05f1720b108a145805a8c83d5ff8248e2d3cbcfa/time_machine-2.15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c596920d6017702a36e3a43fd8110a84e87d6229f30b84bd5640cbae9b5145da", size = 34081 },
{ url = "https://files.pythonhosted.org/packages/a7/c7/9202404f8885257c09c98d3e5186b989b6b482a2983dc24c81bd0333e668/time_machine-2.15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:014589d0edd4aa14f8d63985745565e8cbbe48461d6c004a96000b47f6b44e78", size = 32357 },
{ url = "https://files.pythonhosted.org/packages/4d/79/482a69c31259c3c2efcd9e73ea4a0a4d315103836c1667875612288bca28/time_machine-2.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5ff655716cd13a242eef8cf5d368074e8b396ff86508a5933e7cff4f2b3eb3c2", size = 33742 },
{ url = "https://files.pythonhosted.org/packages/f0/39/89725d12a3552bb9113528d8f9aa7188e1660b377b74e7d72e8ab5eeff06/time_machine-2.15.0-cp39-cp39-win32.whl", hash = "sha256:1168eebd7af7e6e3e2fd378c16ca917b97dd81c89a1f1f9e1daa985c81699d90", size = 19410 },
{ url = "https://files.pythonhosted.org/packages/89/0c/50e86c4a7b72d2bdc658492b13e804f933814f86f34c4350758d1ab87586/time_machine-2.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:c344eb09fcfbf71e5b5847d4f188fec98e1c3a976125ef571eac5f1c39e7a5e5", size = 20281 },
{ url = "https://files.pythonhosted.org/packages/f6/4d/f8ad3b0c50a268a9ea766c9533866bba6a7717a5324c84e356abb7347fa4/time_machine-2.15.0-cp39-cp39-win_arm64.whl", hash = "sha256:899f1a856b3bebb82b6cbc3c0014834b583b83f246b28e462a031ec1b766130b", size = 18387 },
]
[[package]]
name = "tomli"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/e4/1b6cbcc82d8832dd0ce34767d5c560df8a3547ad8cbc427f34601415930a/tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8", size = 16622 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/f7/4da0ffe1892122c9ea096c57f64c2753ae5dd3ce85488802d11b0992cc6d/tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391", size = 13750 },
]
[[package]]
name = "types-pytz"
version = "2024.2.0.20241003"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/66/d0/73aa3063a9ef9881bd7103cb4ae379bfd8fafda0e86b01b694d676313a4b/types-pytz-2024.2.0.20241003.tar.gz", hash = "sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44", size = 5474 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/60/2a2977ce0f91255bbb668350b127a801a06ad37c326a2e5bfd52f03e0784/types_pytz-2024.2.0.20241003-py3-none-any.whl", hash = "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7", size = 5245 },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
]
[[package]]
name = "tzdata"
version = "2024.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
]
[[package]]
name = "zipp"
version = "3.20.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 },
]