diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..dfd0e30 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..14ad007 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Test + +on: + push: + branches: ["master"] + paths: + - ".github/workflows/test.yml" + - "**.py" + - "uv.lock" + - "pyproject.toml" + pull_request: + paths: + - ".github/workflows/test.yml" + - "**.py" + - "uv.lock" + - "pyproject.toml" + +jobs: + test: + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install uv and set the python version ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install the project + run: uv sync --all-extras --dev + - name: Run tests + run: uv run pytest diff --git a/.gitignore b/.gitignore index bc8e7d8..0a57ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,21 @@ -__pycache__ -*.pyc -.idea -env/ -*.swo -*.swp -*.*~ -*.egg -*.egg-info -.#*.* -TAGS -docs -.mypy_cache -.pytest_cache -build -dist -.eggs -.vscode -gmon.out -.vim -pyproject.toml +__pycache__ +*.pyc +.idea +env/ +*.swo +*.swp +*.*~ +*.egg +*.egg-info +.#*.* +TAGS +docs +.mypy_cache +.pytest_cache +build +dist +.eggs +.vscode +gmon.out +.vim +.aider* diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/AUTHORS.md b/AUTHORS.md index 96f9dc0..204dac4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,10 +2,11 @@ ## Development Lead -* Daniel Furtado +* Daniel Furtado ## Contributors +* Kraktus * Nick Schober * Zoltan Ivanfi * Alec Benzer diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 68e47f0..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include AUTHORS.md -include CONTRIBUTING.md -include HISTORY.md -include LICENSE -include README.md - -recursive-include tests * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] - -recursive-include docs *.md conf.py Makefile make.bat *.jpg *.png *.gif diff --git a/README.md b/README.md index c5ca538..ca8b757 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,11 @@ Dataclass CSV makes working with CSV files easier and much better than working w using a list of instances of a dataclass. +## Thanks + +Thank you to all the amazing contributors who have supported this project over the years, with special thanks to [@kraktus](https://github.com/kraktus) for setting up GitHub Actions, improving automation for package creation, and making numerous code enhancements. + + ## Installation ```shell diff --git a/dataclass_csv/__init__.pyi b/dataclass_csv/__init__.pyi deleted file mode 100644 index 973174a..0000000 --- a/dataclass_csv/__init__.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from .dataclass_reader import DataclassReader as DataclassReader -from .dataclass_writer import DataclassWriter as DataclassWriter -from .decorators import ( - accept_whitespaces as accept_whitespaces, - dateformat as dateformat, -) -from .exceptions import CsvValueError as CsvValueError diff --git a/dataclass_csv/dataclass_reader.py b/dataclass_csv/dataclass_reader.py index 6d378dd..213e1e8 100644 --- a/dataclass_csv/dataclass_reader.py +++ b/dataclass_csv/dataclass_reader.py @@ -24,6 +24,8 @@ def strtobool(value: str) -> bool: return value.lower() in trueValues +T = TypeVar("T") + def _verify_duplicate_header_items(header): if header is not None and len(header) == 0: return @@ -42,6 +44,17 @@ def _verify_duplicate_header_items(header): ) +def strtobool(value: str) -> bool: + trueValues = ["true", "yes", "t", "y", "on", "1"] + + validValues = ["false", "no", "f", "n", "off", "0", *trueValues] + + if value.lower() not in validValues: + raise ValueError(f"invalid boolean value {value}") + + return value.lower() in trueValues + + def is_union_type(t): if hasattr(t, "__origin__") and t.__origin__ is Union: return True @@ -60,7 +73,7 @@ class DataclassReader(Generic[T]): def __init__( self, f: Any, - cls: Type[T], + klass: Type[T], fieldnames: Optional[Sequence[str]] = None, restkey: Optional[str] = None, restval: Optional[Any] = None, @@ -72,10 +85,10 @@ def __init__( if not f: raise ValueError("The f argument is required.") - if cls is None or not dataclasses.is_dataclass(cls): - raise ValueError("cls argument needs to be a dataclass.") + if klass is None or not dataclasses.is_dataclass(klass): + raise ValueError("klass argument needs to be a dataclass.") - self._cls = cls + self._cls = klass self._optional_fields = self._get_optional_fields() self._field_mapping: Dict[str, Dict[str, Any]] = {} @@ -88,7 +101,7 @@ def __init__( if validate_header: _verify_duplicate_header_items(self._reader.fieldnames) - self.type_hints = typing.get_type_hints(cls) + self.type_hints = typing.get_type_hints(klass) def _get_optional_fields(self): return [ @@ -120,53 +133,52 @@ def _get_possible_keys(self, fieldname, row): def _get_value(self, row, field): is_field_mapped = False - try: - if field.name in self._field_mapping.keys(): - is_field_mapped = True - key = self._field_mapping.get(field.name) - else: - key = field.name + if field.name in self._field_mapping.keys(): + is_field_mapped = True + key = self._field_mapping.get(field.name) + else: + key = field.name - if key in row.keys(): - value = row[key] - else: + if key in row.keys(): + value = row[key] + else: + try: possible_key = self._get_possible_keys(field.name, row) key = possible_key if possible_key else key value = row[key] - - except KeyError: - if field.name in self._optional_fields: - return self._get_default_value(field) - else: - keyerror_message = f"The value for the column `{field.name}`" - if is_field_mapped: - keyerror_message = f"The value for the mapped column `{key}`" - raise KeyError(f"{keyerror_message} is missing in the CSV file") - else: - if not value and field.name in self._optional_fields: - return self._get_default_value(field) - elif not value and field.name not in self._optional_fields: - raise ValueError(f"The field `{field.name}` is required.") - elif ( - value - and field.type is str - and not len(value.strip()) - and not self._get_metadata_option(field, "accept_whitespaces") - ): - raise ValueError( - ( - f"It seems like the value of `{field.name}` contains " - "only white spaces. To allow white spaces to all " - "string fields, use the @accept_whitespaces " - "decorator. " - "To allow white spaces specifically for the field " - f"`{field.name}` change its definition to: " - f"`{field.name}: str = field(metadata=" - "{'accept_whitespaces': True})`." - ) + except KeyError: + if field.name in self._optional_fields: + return self._get_default_value(field) + else: + keyerror_message = f"The value for the column `{field.name}`" + if is_field_mapped: + keyerror_message = f"The value for the mapped column `{key}`" + raise KeyError(f"{keyerror_message} is missing in the CSV file") + + if not value and field.name in self._optional_fields: + return self._get_default_value(field) + elif not value and field.name not in self._optional_fields: + raise ValueError(f"The field `{field.name}` is required.") + elif ( + value + and field.type is str + and not len(value.strip()) + and not self._get_metadata_option(field, "accept_whitespaces") + ): + raise ValueError( + ( + f"It seems like the value of `{field.name}` contains " + "only white spaces. To allow white spaces to all " + "string fields, use the @accept_whitespaces " + "decorator. " + "To allow white spaces specifically for the field " + f"`{field.name}` change its definition to: " + f"`{field.name}: str = field(metadata=" + "{'accept_whitespaces': True})`." ) - else: - return value + ) + else: + return value def _parse_date_value(self, field, date_value, field_type): dateformat = self._get_metadata_option(field, "dateformat") @@ -231,7 +243,7 @@ def _process_row(self, row) -> T: transformed_value = ( value if isinstance(value, bool) - else strtobool(str(value).strip()) == 1 + else strtobool(str(value).strip()) ) except ValueError as ex: raise CsvValueError(ex, line_number=self._reader.line_num) from None diff --git a/dataclass_csv/dataclass_reader.pyi b/dataclass_csv/dataclass_reader.pyi deleted file mode 100644 index fb721c9..0000000 --- a/dataclass_csv/dataclass_reader.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from .field_mapper import FieldMapper as FieldMapper -from typing import Any, Optional, Sequence, Type, Generic, TypeVar - -T = TypeVar("T") - - -class DataclassReader(Generic[T]): - def __init__( - self, - f: Any, - cls: Type[T], - fieldnames: Optional[Sequence[str]] = ..., - restkey: Optional[str] = ..., - restval: Optional[Any] = ..., - dialect: str = ..., - *args: Any, - **kwds: Any - ) -> None: ... - def __next__(self) -> T: ... - def __iter__(self) -> Any: ... - def map(self, csv_fieldname: str) -> FieldMapper: ... diff --git a/dataclass_csv/dataclass_writer.py b/dataclass_csv/dataclass_writer.py index fdcbb52..4525257 100644 --- a/dataclass_csv/dataclass_writer.py +++ b/dataclass_csv/dataclass_writer.py @@ -1,32 +1,33 @@ import csv import dataclasses -from typing import Type, Dict, Any, List +from typing import Type, Dict, Any, List, Iterable, Generic, TypeVar from .header_mapper import HeaderMapper -class DataclassWriter: + +T = TypeVar("T") + + +class DataclassWriter(Generic[T]): def __init__( self, f: Any, - data: List[Any], - cls: Type[object], + data: Iterable[T], + klass: Type[T], dialect: str = "excel", - **fmtparams: Dict[str, Any], + **fmtparams: Any, ): if not f: raise ValueError("The f argument is required") - if not isinstance(data, list): - raise ValueError("Invalid 'data' argument. It must be a list") - - if not dataclasses.is_dataclass(cls): - raise ValueError("Invalid 'cls' argument. It must be a dataclass") + if not dataclasses.is_dataclass(klass): + raise ValueError("Invalid 'klass' argument. It must be a dataclass") self._data = data - self._cls = cls + self._cls = klass self._field_mapping: Dict[str, str] = dict() - self._fieldnames = [x.name for x in dataclasses.fields(cls)] + self._fieldnames = [x.name for x in dataclasses.fields(klass)] self._writer = csv.writer(f, dialect=dialect, **fmtparams) @@ -48,7 +49,6 @@ def write(self, skip_header: bool = False): self._fieldnames = self._apply_mapping() self._writer.writerow(self._fieldnames) - for item in self._data: if not isinstance(item, self._cls): raise TypeError( diff --git a/dataclass_csv/dataclass_writer.pyi b/dataclass_csv/dataclass_writer.pyi deleted file mode 100644 index 9c0d76f..0000000 --- a/dataclass_csv/dataclass_writer.pyi +++ /dev/null @@ -1,14 +0,0 @@ -from .header_mapper import HeaderMapper as HeaderMapper -from typing import Any, Dict, List, Type - -class DataclassWriter: - def __init__( - self, - f: Any, - data: List[Any], - cls: Type[object], - dialect: str = ..., - **fmtparams: Dict[str, Any], - ) -> None: ... - def write(self, skip_header: bool = ...) -> Any: ... - def map(self, propname: str) -> HeaderMapper: ... diff --git a/dataclass_csv/decorators.py b/dataclass_csv/decorators.py index 67f4255..0137c5b 100644 --- a/dataclass_csv/decorators.py +++ b/dataclass_csv/decorators.py @@ -23,14 +23,15 @@ def dateformat(date_format: str) -> Callable[[F], F]: if not date_format or not isinstance(date_format, str): raise ValueError("Invalid value for the date_format argument") - def func(cls): - cls.__dateformat__ = date_format - return cls + def func(klass): + klass.__dateformat__ = date_format + return klass return func +KLASS = TypeVar("KLASS", bound=Type[Any]) -def accept_whitespaces(_cls: Type[Any] = None) -> Callable[[F], F]: +def accept_whitespaces(klass: KLASS) -> KLASS: """The accept_whitespaces decorator tells the `DataclassReader` that `str` fields defined in the `dataclass` should accept values containing only white spaces. @@ -47,11 +48,5 @@ def accept_whitespaces(_cls: Type[Any] = None) -> Callable[[F], F]: >>> brithday: datetime """ - def func(cls): - cls.__accept_whitespaces__ = True - return cls - - if _cls: - return func(_cls) - - return func + klass.__accept_whitespaces__ = True + return klass diff --git a/dataclass_csv/decorators.pyi b/dataclass_csv/decorators.pyi deleted file mode 100644 index 3f0a372..0000000 --- a/dataclass_csv/decorators.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Any, Callable, Type, TypeVar - -F = TypeVar("F", bound=Callable[..., Any]) - -def dateformat(date_format: str) -> Callable[[F], F]: ... -def accept_whitespaces(_cls: Type[Any] = ...) -> Callable[[F], F]: ... diff --git a/dataclass_csv/exceptions.pyi b/dataclass_csv/exceptions.pyi deleted file mode 100644 index 7040a34..0000000 --- a/dataclass_csv/exceptions.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Any - -class CsvValueError(Exception): - error: Any = ... - line_number: Any = ... - def __init__(self, error: Any, line_number: int) -> None: ... diff --git a/dataclass_csv/field_mapper.pyi b/dataclass_csv/field_mapper.pyi deleted file mode 100644 index 66a04a9..0000000 --- a/dataclass_csv/field_mapper.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from typing import Any, Callable - -class FieldMapper: - to: Any = ... - def __init__(self, callback: Callable[[str], None]) -> None: ... diff --git a/dataclass_csv/header_mapper.pyi b/dataclass_csv/header_mapper.pyi deleted file mode 100644 index c2cc1b3..0000000 --- a/dataclass_csv/header_mapper.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from typing import Any, Callable - -class HeaderMapper: - to: Any = ... - def __init__(self, callback: Callable[[str], None]) -> None: ... diff --git a/dataclass_csv/py.typed b/dataclass_csv/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1490a69 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,56 @@ +[project] +name = "dataclass-csv" +version = "1.4.0" +description = "Map CSV data into dataclasses" +readme = "README.md" +license-expression = "BSD-3-Clause" +requires-python = ">=3.7" +dependencies = [] +authors = [ + {name = "Daniel Furtado", email = "daniel@dfurtado.com"}, +] +keywords = ["dataclass", "dataclasses", "csv", "dataclass-csv"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Unix", + "Operating System :: POSIX", + "Environment :: Console", +] + + +[project.urls] +Homepage = "https://github.com/dfurtado/dataclass-csv" +"Issue Tracker" = "https://github.com/dfurtado/dataclass-csv/issues" + +[dependency-groups] +dev = [ + "pyright>=1.1.392.post0", + "pytest>=7.4.4", +] + + +# pyproject.toml +[tool.pytest.ini_options] +testpaths = [ + "tests" +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["dataclass_csv"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 12e95da..0000000 --- a/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -exceptiongroup==1.3.0 -flake8==7.3.0 -iniconfig==2.3.0 -mccabe==0.7.0 -mypy==1.18.2 -mypy_extensions==1.1.0 -packaging==25.0 -pathspec==0.12.1 -pluggy==1.6.0 -pycodestyle==2.14.0 -pyflakes==3.4.0 -Pygments==2.19.2 -pytest==8.4.2 -tomli==2.3.0 -typing_extensions==4.15.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 43bd1f6..0000000 --- a/setup.cfg +++ /dev/null @@ -1,26 +0,0 @@ -[bumpversion] -current_version = 1.4.0 -commit = True -tag = True - -[bumpversion:file:setup.py] -search = version='{current_version}' -replace = version='{new_version}' - -[bumpversion:file:easycsv/__init__.py] -search = __version__ = '{current_version}' -replace = __version__ = '{new_version}' - -[bdist_wheel] -universal = 1 - -[flake8] -exclude = docs -max-line-length = 88 - -[aliases] -# Define setup.py command aliases here -test = pytest - -[tool:pytest] -collect_ignore = ['setup.py'] diff --git a/setup.py b/setup.py deleted file mode 100644 index b998ace..0000000 --- a/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""The setup script.""" - -from setuptools import setup, find_packages - -with open("README.md") as readme_file: - readme = readme_file.read() - -with open("HISTORY.md") as history_file: - history = history_file.read() - -requirements = [] - -setup_requirements = [ - "pytest-runner", -] - -test_requirements = [ - "pytest", -] - -setup( - author="Daniel Furtado", - author_email="daniel@dfurtado.com", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Unix", - "Operating System :: POSIX", - "Environment :: Console", - ], - description="Map CSV data into dataclasses", - install_requires=requirements, - license="BSD license", - long_description=readme + "\n\n" + history, - long_description_content_type="text/markdown", - include_package_data=True, - keywords="dataclass dataclasses csv dataclass-csv", - name="dataclass-csv", - packages=find_packages(include=["dataclass_csv"]), - package_data={"dataclass_csv": ["py.typed", "*.pyi"]}, - setup_requires=setup_requirements, - test_suite="tests", - tests_require=test_requirements, - url="https://github.com/dfurtado/dataclass-csv", - version="1.4.0", - zip_safe=False, -) diff --git a/tests/conftest.py b/tests/conftest.py index 6190666..fc049df 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,12 @@ from csv import DictWriter +from typing import Any, Dict, List, Union import pytest @pytest.fixture() def create_csv(tmpdir_factory): - def func(data, fieldnames=None, filename="user.csv", factory=tmpdir_factory): + def func(data: Union[Dict[str, Any], List[Dict[str, Any]]], fieldnames=None, filename="user.csv", factory=tmpdir_factory): assert data @@ -18,8 +19,10 @@ def func(data, fieldnames=None, filename="user.csv", factory=tmpdir_factory): with file.open("w") as f: writer = DictWriter(f, fieldnames=header) writer.writeheader() - addrow = writer.writerows if isinstance(data, list) else writer.writerow - addrow(data) + if isinstance(data, list): + writer.writerows(data) + else: + writer.writerow(data) return file diff --git a/tests/test_dataclass_reader.py b/tests/test_dataclass_reader.py index 2c3df49..dc4c559 100644 --- a/tests/test_dataclass_reader.py +++ b/tests/test_dataclass_reader.py @@ -36,7 +36,7 @@ def test_reader_with_none_class(create_csv): with csv_file.open() as f: with pytest.raises(ValueError): - DataclassReader(f, None) + DataclassReader(f, None) # type: ignore def test_reader_with_none_file(): diff --git a/tests/test_dataclass_writer.py b/tests/test_dataclass_writer.py index 4b54517..3c874e5 100644 --- a/tests/test_dataclass_writer.py +++ b/tests/test_dataclass_writer.py @@ -50,7 +50,7 @@ def test_with_a_empty_cls_value(tmpdir_factory): with tempfile.open("w") as f: with pytest.raises(ValueError): - DataclassWriter(f, users, None) + DataclassWriter(f, users, None) # type: ignore def test_invalid_file_value(tmpdir_factory): @@ -61,12 +61,16 @@ def test_invalid_file_value(tmpdir_factory): with pytest.raises(ValueError): DataclassWriter(None, users, User) - -def test_with_data_not_a_list(tmpdir_factory): +def test_with_iterable(tmpdir_factory): tempfile = tmpdir_factory.mktemp("data").join("user_001.csv") - - users = User(name="test", age=40) + users_dict = {"test": User(name="test", age=40)} with tempfile.open("w") as f: - with pytest.raises(ValueError): - DataclassWriter(f, users, User) + DataclassWriter(f, users_dict.values(), User).write() + + with tempfile.open() as f: + reader = DataclassReader(f, User) + saved_users = list(reader) + + assert len(saved_users) > 0 + assert saved_users[0].name == users_dict["test"].name diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..eb4b8ba --- /dev/null +++ b/uv.lock @@ -0,0 +1,269 @@ +version = 1 +revision = 3 +requires-python = ">=3.7" +resolution-markers = [ + "python_full_version >= '3.8'", + "python_full_version < '3.8'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dataclass-csv" +version = "1.4.0" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "8.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.392.post0" }, + { name = "pytest", specifier = ">=7.4.4" }, +] + +[[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, upload-time = "2024-07-12T22:26:00.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "zipp", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569, upload-time = "2023-06-18T21:44:35.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934, upload-time = "2023-06-18T21:44:33.441Z" }, +] + +[[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, upload-time = "2023-01-07T11:08:11.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +version = "24.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882, upload-time = "2024-03-10T09:39:28.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488, upload-time = "2024-03-10T09:39:25.947Z" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613, upload-time = "2023-06-21T09:12:28.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695, upload-time = "2023-06-21T09:12:27.397Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.392.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "typing-extensions", version = "4.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/df/3c6f6b08fba7ccf49b114dfc4bb33e25c299883fd763f93fad47ef8bc58d/pyright-1.1.392.post0.tar.gz", hash = "sha256:3b7f88de74a28dcfa90c7d90c782b6569a48c2be5f9d4add38472bdaac247ebd", size = 3789911, upload-time = "2025-01-15T15:01:20.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/b1/a18de17f40e4f61ca58856b9ef9b0febf74ff88978c3f7776f910071f567/pyright-1.1.392.post0-py3-none-any.whl", hash = "sha256:252f84458a46fa2f0fd4e2f91fc74f50b9ca52c757062e93f6c250c0d8329eb2", size = 5595487, upload-time = "2025-01-15T15:01:17.775Z" }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.8' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "iniconfig", marker = "python_full_version < '3.8'" }, + { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pluggy", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.8' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version >= '3.8' and python_full_version < '3.11'" }, + { name = "iniconfig", marker = "python_full_version >= '3.8'" }, + { name = "packaging", version = "24.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "tomli", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919, upload-time = "2024-12-01T12:54:25.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083, upload-time = "2024-12-01T12:54:19.735Z" }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164, upload-time = "2022-02-08T10:54:04.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757, upload-time = "2022-02-08T10:54:02.017Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876, upload-time = "2023-07-02T14:20:55.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232, upload-time = "2023-07-02T14:20:53.275Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454, upload-time = "2023-02-25T02:17:22.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758, upload-time = "2023-02-25T02:17:20.807Z" }, +]