From 943d2f815ae2ef2281cb2aa2e92f33cf7101cf8d Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 5 Sep 2022 14:09:19 +0000 Subject: [PATCH 01/78] version 3.0 --- changelog.txt | 6 +++--- cstruct/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6f0e3e2..760c88f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -128,10 +128,10 @@ - Fix compare with None -### 2.4 +### 3.0 -2022-XX-XX +2022-09-05 ### Added -- flexible array support +- Flexible array support diff --git a/cstruct/__init__.py b/cstruct/__init__.py index f9179c2..8e57f3e 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = 'Andrea Bonomi ' __license__ = 'MIT' -__version__ = '2.3' +__version__ = '3.0' __date__ = '15 August 2013' import struct From 400c88edaabe60ee1b7a269df8395a8f8bc745a3 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 5 Sep 2022 16:19:14 +0200 Subject: [PATCH 02/78] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 20d12ef..2cac70d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ C-style structs for Python [![Downloads](https://pepy.tech/badge/cstruct/month)](https://pepy.tech/project/cstruct) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Known Vulnerabilities](https://snyk.io/test/github/andreax79/python-cstruct/badge.svg)](https://snyk.io/test/github/andreax79/python-cstruct) +[![Documentation](https://readthedocs.org/projects/python-cstruct/badge/?version=latest)](https://python-cstruct.readthedocs.io/en/latest/) Convert C struct/union definitions into Python classes with methods for serializing/deserializing. From 2e64ffdc989ff98735672907f5151d5267f5ef6c Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 13 Sep 2022 06:08:10 +0000 Subject: [PATCH 03/78] Make CStruct/MemCStruct Pickle Friendly --- cstruct/abstract.py | 20 +++++++++++++++++ tests/test_pickle.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tests/test_pickle.py diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 50bf024..45be23f 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -229,3 +229,23 @@ def __str__(self) -> str: def __repr__(self) -> str: # pragma: no cover return self.__str__() + + def __getstate__(self) -> bytes: + """ + This method is called and the returned object is pickled + as the contents for the instance, instead of the contents of + the instance’s dictionary + + Returns: + bytes: The packed structure + """ + return self.pack() + + def __setstate__(self, state: bytes) -> bool: + """ + This method it is called with the unpickled state + + Args: + state: bytes to be unpacked + """ + return self.unpack(state) diff --git a/tests/test_pickle.py b/tests/test_pickle.py new file mode 100644 index 0000000..14789a0 --- /dev/null +++ b/tests/test_pickle.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +import pickle +import cstruct + + +class Position(cstruct.CStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct { + unsigned char head; + unsigned char sector; + unsigned char cyl; + } + """ + + +class MemPosition(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct { + unsigned char head; + unsigned char sector; + unsigned char cyl; + } + """ + + +def test_pickle(): + # pickle an object + original_pos = Position(head=254, sector=63, cyl=134) + pickled_bytes = pickle.dumps(original_pos) + + # reconstitute a pickled object + reconstituted_pos = pickle.loads(pickled_bytes) + + assert reconstituted_pos.head == original_pos.head + assert reconstituted_pos.sector == original_pos.sector + assert reconstituted_pos.cyl == original_pos.cyl + + +def test_mem_pickle(): + # pickle an object + original_pos = MemPosition(head=254, sector=63, cyl=134) + pickled_bytes = pickle.dumps(original_pos) + + # reconstitute a pickled object + reconstituted_pos = pickle.loads(pickled_bytes) + + assert reconstituted_pos.head == original_pos.head + assert reconstituted_pos.sector == original_pos.sector + assert reconstituted_pos.cyl == original_pos.cyl From 26c15be27a0bccf899100e4ce485d482649d66ef Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 13 Sep 2022 06:18:59 +0000 Subject: [PATCH 04/78] Add coverage configuration --- Makefile | 2 +- pyproject.toml | 6 ++++++ requirements-dev.txt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 02d0e21..aa727c8 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ clean: -rm -rf bin lib share pyvenv.cfg coverage: - python3 -m coverage run --source=cstruct setup.py test && python3 -m coverage report -m + python3 -m coverage run setup.py test && python3 -m coverage report -m .PHONY: docs docs: diff --git a/pyproject.toml b/pyproject.toml index c70aaf6..70b86b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,8 @@ [tool.black] line-length = 132 + +[tool.coverage.run] +source = ["cstruct"] + +[tool.coverage.report] +exclude_lines = [ "# pragma: no cover", "if TYPE_CHECKING:" ] diff --git a/requirements-dev.txt b/requirements-dev.txt index 8bf236b..c9eff59 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -coverage +coverage[toml] flake8 mypy tox From 6236ff6d96e4202a2068c1846b87bc7ac7de27b4 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 13 Sep 2022 06:36:52 +0000 Subject: [PATCH 05/78] Update README.md --- README.md | 76 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 2cac70d..37b5b98 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ is generated. The class offers the method "unpack" for deserializing an array of bytes into a Python object and the method "pack" for serializing the values into an array of bytes. +[Api Documentation](https://readthedocs.org/projects/python-cstruct/badge/?version=latest) + Example ------- @@ -31,53 +33,63 @@ import cstruct class Position(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __def__ = """ - struct { - unsigned char head; - unsigned char sector; - unsigned char cyl; - } + __struct__ = """ + unsigned char head; + unsigned char sector; + unsigned char cyl; """ + class Partition(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __def__ = """ - struct { - unsigned char status; /* 0x80 - active */ - struct Position start; - unsigned char partition_type; - struct Position end; - unsigned int start_sect; /* starting sector counting from 0 */ - unsigned int sectors; /* nr of sectors in partition */ - } + __struct__ = """ + #define ACTIVE_FLAG 0x80 + + unsigned char status; /* 0x80 - active */ + struct Position start; + unsigned char partition_type; + struct Position end; + unsigned int start_sect; /* starting sector counting from 0 */ + unsigned int sectors; /* nr of sectors in partition */ """ def print_info(self): - print("bootable: %s" % ((self.status & 0x80) and "Y" or "N")) - print("partition_type: %02X" % self.partition_type) - print("start: head: %X sectory: %X cyl: %X" % (self.start.head, self.start.sector, self.start.cyl)) - print("end: head: %X sectory: %X cyl: %X" % (self.end.head, self.end.sector, self.end.cyl)) - print("starting sector: %08X" % self.start_sect) - print("size MB: %s" % (self.sectors / 2 / 1024)) + print(f"bootable: {'Y' if self.status & cstruct.getdef('ACTIVE_FLAG') else 'N'}") + print(f"partition_type: {self.partition_type:02X}") + print(f"start: head: {self.start.head:X} sectory: {self.start.sector:X} cyl: {self.start.cyl:X}") + print(f"end: head: {self.end.head:X} sectory: {self.end.sector:X} cyl: {self.end.cyl:X}") + print(f"starting sector: {self.start_sect:08x}") + print(f"size MB: {self.sectors / 2 / 1024}") + class MBR(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __def__ = """ - struct { - char unused[440]; - unsigned char disk_signature[4]; - unsigned char usualy_nulls[2]; - struct Partition partitions[4]; - char signature[2]; - } + __struct__ = """ + #define MBR_SIZE 512 + #define MBR_DISK_SIGNATURE_SIZE 4 + #define MBR_USUALY_NULLS_SIZE 2 + #define MBR_SIGNATURE_SIZE 2 + #define MBR_BOOT_SIGNATURE 0xaa55 + #define MBR_PARTITIONS_NUM 4 + #define MBR_PARTITIONS_SIZE (sizeof(Partition) * MBR_PARTITIONS_NUM) + #define MBR_UNUSED_SIZE (MBR_SIZE - MBR_DISK_SIGNATURE_SIZE - MBR_USUALY_NULLS_SIZE - MBR_PARTITIONS_SIZE - MBR_SIGNATURE_SIZE) + + char unused[MBR_UNUSED_SIZE]; + unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; + unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; + struct Partition partitions[MBR_PARTITIONS_NUM]; + uint16 signature; """ + @property + def disk_signature_str(self): + return "".join(reversed([f"{x:02x}" for x in self.disk_signature])) + def print_info(self): - print("disk signature: %s" % "".join(["%02X" % x for x in self.disk_signature])) - print("usualy nulls: %s" % "".join(["%02X" % x for x in self.usualy_nulls])) + print(f"disk signature: {self.disk_signature_str}") for i, partition in enumerate(self.partitions): print("") - print("partition: %s" % i) + print(f"partition: {i}") partition.print_info() disk = "mbr" From 492bfc37eb6a4114e39b872262ef448cd770da87 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 13 Sep 2022 06:42:00 +0000 Subject: [PATCH 06/78] Fix doc strings --- cstruct/abstract.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 45be23f..f5aeca9 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -148,6 +148,9 @@ def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> Non Args: flexible_array_length: flexible array length + + Raises: + CStructException: If flexible array is not present in the structure """ if flexible_array_length is not None: # Search for the flexible array @@ -185,7 +188,12 @@ def unpack_from( raise NotImplementedError def pack(self) -> bytes: # pragma: no cover - "Pack the structure data into bytes" + """ + Pack the structure data into bytes + + Returns: + bytes: The packed structure + """ raise NotImplementedError def clear(self) -> None: From 9790a914d17a802144969501a68d95497d5a469e Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 13 Sep 2022 06:42:29 +0000 Subject: [PATCH 07/78] version 3.1 --- README.md | 2 +- changelog.txt | 8 ++++++++ cstruct/__init__.py | 7 ++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 37b5b98..44e9095 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ is generated. The class offers the method "unpack" for deserializing an array of bytes into a Python object and the method "pack" for serializing the values into an array of bytes. -[Api Documentation](https://readthedocs.org/projects/python-cstruct/badge/?version=latest) +[Api Documentation](https://python-cstruct.readthedocs.io/en/latest/) Example ------- diff --git a/changelog.txt b/changelog.txt index 760c88f..becb010 100644 --- a/changelog.txt +++ b/changelog.txt @@ -135,3 +135,11 @@ ### Added - Flexible array support + +### 3.1 + +2022-09-13 + +### Added + +- Make CStruct/MemCStruct Pickle Friendly diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 8e57f3e..331f9d1 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = 'Andrea Bonomi ' __license__ = 'MIT' -__version__ = '3.0' +__version__ = '3.1' __date__ = '15 August 2013' import struct @@ -132,10 +132,7 @@ def sizeof(type_: str) -> int: def parse( - __struct__: str, - __cls__: Optional[Type[AbstractCStruct]] = None, - __name__: Optional[str] = None, - **kargs: Dict[str, Any] + __struct__: str, __cls__: Optional[Type[AbstractCStruct]] = None, __name__: Optional[str] = None, **kargs: Dict[str, Any] ) -> Optional[Type[AbstractCStruct]]: """ Return a new class mapping a C struct/union definition. From 165d12de7a2b28457959f8b3b26976c2df55c207 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 13 Sep 2022 14:04:50 +0000 Subject: [PATCH 08/78] Tests added --- Makefile | 2 +- requirements-dev.txt | 1 + tests/test_define.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index aa727c8..8048837 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ clean: -rm -rf bin lib share pyvenv.cfg coverage: - python3 -m coverage run setup.py test && python3 -m coverage report -m + @pytest --cov --cov-report=term-missing .PHONY: docs docs: diff --git a/requirements-dev.txt b/requirements-dev.txt index c9eff59..ff893de 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ coverage[toml] +pytest-cov flake8 mypy tox diff --git a/tests/test_define.py b/tests/test_define.py index a98ec42..f840ab5 100644 --- a/tests/test_define.py +++ b/tests/test_define.py @@ -28,6 +28,7 @@ import pytest import cstruct from cstruct import define, undef, sizeof, typedef +from cstruct.exceptions import ParserError class Position(cstruct.CStruct): @@ -65,3 +66,31 @@ def test_define(): def test_typedef(): typedef('int', 'integer') assert sizeof('integer') == 4 + + +def test_invalid_type(): + with pytest.raises(ParserError): + + class Invalid(cstruct.CStruct): + __def__ = """ + struct { + unsigned xxx yyy; + } + """ + + +def test_invalid_define(): + with pytest.raises(ParserError): + cstruct.parse(""" + #define xxx yyy zzz + """) + + +def test_invalid_struct(): + with pytest.raises(ParserError): + cstruct.parse(""" + struct { + int a; + int; + } + """) From ceb71ced3c2d1099b5851ef77be7251e1a09180e Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 23 Oct 2022 07:49:48 +0000 Subject: [PATCH 09/78] Fix padding tests on 32bit architectures --- changelog.txt | 8 +++++ cstruct/__init__.py | 2 +- setup.py | 3 -- tests/test_alignment.py | 67 ++++++++++++++++++++++++++++++----------- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/changelog.txt b/changelog.txt index becb010..8797a5a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -143,3 +143,11 @@ ### Added - Make CStruct/MemCStruct Pickle Friendly + +### 3.2 + +2022-10-23 + +### Fix + +- Fix padding tests on 32bit architectures diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 331f9d1..52f213f 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = 'Andrea Bonomi ' __license__ = 'MIT' -__version__ = '3.1' +__version__ = '3.2' __date__ = '15 August 2013' import struct diff --git a/setup.py b/setup.py index 23b1348..f8814ff 100644 --- a/setup.py +++ b/setup.py @@ -17,9 +17,6 @@ def readme(): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/tests/test_alignment.py b/tests/test_alignment.py index 252c353..b7cc3e1 100644 --- a/tests/test_alignment.py +++ b/tests/test_alignment.py @@ -25,8 +25,11 @@ # # ***************************************************************************** +import sys from cstruct import sizeof, typedef, define, CStruct, NATIVE_ORDER +IS_64BITS = sys.maxsize > 2**32 + define("UT_NAMESIZE", 32) define("UT_LINESIZE", 32) define("UT_HOSTSIZE", 256) @@ -196,21 +199,36 @@ def test_utmp_sizeof(): def test_foo1_sizeof(): - assert Foo1.__fields_types__['p'].padding == 0 - assert Foo1.__fields_types__['c'].padding == 0 - assert Foo1.__fields_types__['x'].padding == 7 - assert sizeof("struct Foo1") == 24 - assert Foo1().size == 24 + if IS_64BITS: + assert Foo1.__fields_types__['p'].padding == 0 + assert Foo1.__fields_types__['c'].padding == 0 + assert Foo1.__fields_types__['x'].padding == 7 + assert sizeof("struct Foo1") == 24 + assert Foo1().size == 24 + else: + assert Foo1.__fields_types__['p'].padding == 0 + assert Foo1.__fields_types__['c'].padding == 0 + assert Foo1.__fields_types__['x'].padding == 3 + assert sizeof("struct Foo1") == 12 + assert Foo1().size == 12 def test_foo2_sizeof(): - assert sizeof("struct Foo2") == 24 - assert Foo2().size == 24 + if IS_64BITS: + assert sizeof("struct Foo2") == 24 + assert Foo2().size == 24 + else: + assert sizeof("struct Foo2") == 16 + assert Foo2().size == 16 def test_foo3_sizeof(): - assert sizeof("struct Foo3") == 16 - assert Foo3().size == 16 + if IS_64BITS: + assert sizeof("struct Foo3") == 16 + assert Foo3().size == 16 + else: + assert sizeof("struct Foo3") == 8 + assert Foo3().size == 8 def test_foo4_sizeof(): @@ -219,15 +237,28 @@ def test_foo4_sizeof(): def test_foo5_sizeof(): - assert Foo5.__fields_types__['c'].padding == 0 - assert Foo5.__fields_types__['inner'].padding == 7 - assert sizeof("struct Foo5") == 24 - assert Foo5().size == 24 + if IS_64BITS: + assert Foo5.__fields_types__['c'].padding == 0 + assert Foo5.__fields_types__['inner'].padding == 7 + assert sizeof("struct Foo5") == 24 + assert Foo5().size == 24 + else: + assert Foo5.__fields_types__['c'].padding == 0 + assert Foo5.__fields_types__['inner'].padding == 3 + assert sizeof("struct Foo5") == 12 + assert Foo5().size == 12 def test_foo10_sizeof(): - assert Foo10.__fields_types__['c'].padding == 0 - assert Foo10.__fields_types__['p'].padding == 7 - assert Foo10.__fields_types__['s'].padding == 0 - assert sizeof("struct Foo10") == 24 - assert Foo10().size == 24 + if IS_64BITS: + assert Foo10.__fields_types__['c'].padding == 0 + assert Foo10.__fields_types__['p'].padding == 7 + assert Foo10.__fields_types__['s'].padding == 0 + assert sizeof("struct Foo10") == 24 + assert Foo10().size == 24 + else: + assert Foo10.__fields_types__['c'].padding == 0 + assert Foo10.__fields_types__['p'].padding == 3 + assert Foo10.__fields_types__['s'].padding == 0 + assert sizeof("struct Foo10") == 12 + assert Foo10().size == 12 From 3af7f3917c6ab6fbdfd8009ddb8a5b0f88104b97 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 24 Oct 2022 08:15:36 +0000 Subject: [PATCH 10/78] 32bit architecture test environment --- Makefile | 4 ++++ docker/i386/Dockerfile | 14 ++++++++++++++ docker/i386/Makefile | 31 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 docker/i386/Dockerfile create mode 100644 docker/i386/Makefile diff --git a/Makefile b/Makefile index 8048837..6b9e616 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ help: @echo - make docs ------- Make docs @echo - make lint ------- Run lint @echo - make test ------- Run test + @echo - make test-32bit - Run test on 32bit architecture @echo - make typecheck -- Typecheck @echo - make venv ------- Create virtual environment @@ -32,6 +33,9 @@ lint: test: pytest +test-32bit: + @make -C docker/i386 test + typecheck: mypy --strict --no-warn-unused-ignores cstruct diff --git a/docker/i386/Dockerfile b/docker/i386/Dockerfile new file mode 100644 index 0000000..9037dda --- /dev/null +++ b/docker/i386/Dockerfile @@ -0,0 +1,14 @@ +FROM i386/ubuntu + +RUN apt-get update && \ + apt-get -y install \ + python3.6 \ + python3.6-dev \ + python3.6-distutils \ + curl \ + make && \ + rm -rf /var/lib/apt/lists/* +RUN curl -sSL https://bootstrap.pypa.io/pip/3.6/get-pip.py -o get-pip.py && \ + python3.6 get-pip.py +RUN pip install pytest +WORKDIR /app diff --git a/docker/i386/Makefile b/docker/i386/Makefile new file mode 100644 index 0000000..9431f7e --- /dev/null +++ b/docker/i386/Makefile @@ -0,0 +1,31 @@ +PROJECT=cstruct +BASENAME=test-i386 +IMAGE_NAME=${PROJECT}-${BASENAME} + +.PHONY: help build push all + +help: + @echo "- make build Build docker image" + @echo "- make test Build and run tests" + @echo "- make shell Run interactive shell" + +.DEFAULT_GOAL := help + +build: + @DOCKER_BUILDKIT=1 docker build --tag ${IMAGE_NAME}:latest . + +test: build + @docker run --rm -it \ + --mount type=bind,source=$$PWD/../..,target=/app \ + --hostname=$(BASENAME) \ + ${IMAGE_NAME} \ + pytest + +shell: + @docker run --rm -it \ + --mount type=bind,source=$$PWD/../..,target=/app \ + --hostname=$(BASENAME) \ + ${IMAGE_NAME} \ + bash -i + +all: build From ecc321bf06fb47d967142c2bc2bb18496e53c0a3 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 24 Oct 2022 08:20:02 +0000 Subject: [PATCH 11/78] Fix size test on 32bit architectures --- changelog.txt | 12 ++++++++++++ cstruct/__init__.py | 2 +- tests/test_define.py | 8 +++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 8797a5a..aaba19e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -151,3 +151,15 @@ ### Fix - Fix padding tests on 32bit architectures + +### 3.3 + +2022-10-24 + +### Added + +- Add 32bit test environment + +### Fix + +- Fix padding tests on 32bit architectures diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 52f213f..d20062e 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = 'Andrea Bonomi ' __license__ = 'MIT' -__version__ = '3.2' +__version__ = '3.3' __date__ = '15 August 2013' import struct diff --git a/tests/test_define.py b/tests/test_define.py index f840ab5..a47e3a8 100644 --- a/tests/test_define.py +++ b/tests/test_define.py @@ -25,11 +25,14 @@ # # ***************************************************************************** +import sys import pytest import cstruct from cstruct import define, undef, sizeof, typedef from cstruct.exceptions import ParserError +IS_64BITS = sys.maxsize > 2**32 + class Position(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN @@ -45,7 +48,10 @@ class Position(cstruct.CStruct): def test_sizeof(): assert sizeof('int') == 4 define('INIT_THREAD_SIZE', 2048 * sizeof('long')) - assert cstruct.DEFINES['INIT_THREAD_SIZE'] == 16384 + if IS_64BITS: + assert cstruct.DEFINES['INIT_THREAD_SIZE'] == 16384 + else: + assert cstruct.DEFINES['INIT_THREAD_SIZE'] == 8192 assert sizeof('struct Position') == 3 assert sizeof('struct Position') == len(Position) assert sizeof(Position) == 3 From 6acd68264cbed8d70896016fda7568dd42be78c2 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Mon, 31 Oct 2022 17:08:13 +0100 Subject: [PATCH 12/78] Fix: c_parser ignored scopes with nameless inline struct The `parse_type` would continue looking for tokens even when the c_type ended with a `{`. This would mean that the following resulted in an exception: ``` __struct__ = """ struct { int i; } s; """ ``` `parse_type` now ignores any following tokens if `c_type` ends with an `{` - meaning the tokens afterwards are not element of the current type scope. Signed-off-by: Sophie Tyalie --- cstruct/c_parser.py | 72 +++++++++++++++++++++++-------------------- tests/test_cstruct.py | 5 ++- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index d1a947c..476067a 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -78,43 +78,47 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt # signed/unsigned/struct if c_type in ['signed', 'unsigned', 'struct', 'union'] and len(tokens) > 1: c_type = c_type + " " + tokens.pop() - next_token = tokens.pop() - # short int, long int, or long long - if next_token in ['int', 'long']: - c_type = c_type + " " + next_token - next_token = tokens.pop() - # void * - if next_token.startswith("*"): - next_token = next_token[1:] - c_type = 'void *' - # parse length + vlen = 1 flexible_array = False - if "[" in next_token: - t = next_token.split("[") - if len(t) != 2: - raise ParserError("Error parsing: " + next_token) - next_token = t[0].strip() - vlen_part = t[1] - vlen_expr = [] - while not vlen_part.endswith("]"): + + if not c_type.endswith("{"): + next_token = tokens.pop() + # short int, long int, or long long + if next_token in ['int', 'long']: + c_type = c_type + " " + next_token + next_token = tokens.pop() + # void * + if next_token.startswith("*"): + next_token = next_token[1:] + c_type = 'void *' + # parse length + if "[" in next_token: + t = next_token.split("[") + if len(t) != 2: + raise ParserError("Error parsing: " + next_token) + next_token = t[0].strip() + vlen_part = t[1] + vlen_expr = [] + while not vlen_part.endswith("]"): + vlen_expr.append(vlen_part.split("]")[0].strip()) + vlen_part = tokens.pop() + t_vlen = vlen_part.split("]")[0].strip() vlen_expr.append(vlen_part.split("]")[0].strip()) - vlen_part = tokens.pop() - t_vlen = vlen_part.split("]")[0].strip() - vlen_expr.append(vlen_part.split("]")[0].strip()) - t_vlen = " ".join(vlen_expr) - if not t_vlen: - flexible_array = True - vlen = 0 - else: - try: - vlen = c_eval(t_vlen) - except (ValueError, TypeError): - vlen = int(t_vlen) - tokens.push(next_token) - # resolve typedefs - while c_type in TYPEDEFS: - c_type = TYPEDEFS[c_type] + t_vlen = " ".join(vlen_expr) + if not t_vlen: + flexible_array = True + vlen = 0 + else: + try: + vlen = c_eval(t_vlen) + except (ValueError, TypeError): + vlen = int(t_vlen) + tokens.push(next_token) + # resolve typedefs + while c_type in TYPEDEFS: + c_type = TYPEDEFS[c_type] + # calculate fmt if c_type.startswith('struct ') or c_type.startswith('union '): # struct/union c_type, tail = c_type.split(' ', 1) diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index cd7350b..a3653a1 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -75,10 +75,12 @@ class MBR(cstruct.CStruct): class Dummy(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN __def__ = """ + struct { struct { + int i; + } s; char c; char vc[10]; - int i; int vi[10]; long long l; long vl[10]; @@ -210,6 +212,7 @@ def test_inline(): def test_dummy(): dummy = Dummy() + dummy.c = b'A' dummy.vc = b'ABCDEFGHIJ' dummy.i = 123456 From a4fa6fb0222aff30f2f6bf5f8d883e6256b6a86b Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 31 Oct 2022 16:49:45 +0000 Subject: [PATCH 13/78] add __repr__ method to FieldType --- cstruct/field.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cstruct/field.py b/cstruct/field.py index 2597b83..287a027 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -220,3 +220,6 @@ def align_filed_offset(self) -> None: def copy(self) -> "FieldType": "Return a shallow copy of this FieldType" return copy.copy(self) + + def __repr__(self) -> str: # pragma: no cover + return repr(self.__dict__) From f337b0d2815696d645f9c90336cf5a66cd5e9a5d Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 08:00:40 +0000 Subject: [PATCH 14/78] Add check for duplicate members --- cstruct/c_parser.py | 6 ++++-- tests/test_cstruct.py | 10 ++++++++++ tests/test_memcstruct.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 476067a..e4cfbb2 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -78,7 +78,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt # signed/unsigned/struct if c_type in ['signed', 'unsigned', 'struct', 'union'] and len(tokens) > 1: c_type = c_type + " " + tokens.pop() - + vlen = 1 flexible_array = False @@ -195,6 +195,8 @@ def parse_struct( raise CStructException("Flexible array member must be the last member of such a struct") field_type = parse_type(tokens, __cls__, __byte_order__, offset) vname = tokens.pop() + if vname in fields_types: + raise ParserError("Duplicate member '{}'".format(vname)) fields_types[vname] = field_type # calculate the max field size (for the alignment) max_alignment = max(max_alignment, field_type.alignment) @@ -204,7 +206,7 @@ def parse_struct( offset = field_type.offset + field_type.vsize t = tokens.pop() if t != ';': - raise ParserError("; expected but %s found" % t) + raise ParserError("; expected but {} found".format(t)) if __is_union__: # C union # Calculate the sizeof union as size of its largest element diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index a3653a1..b5eab9f 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -25,11 +25,13 @@ # # ***************************************************************************** +import pytest import cstruct from cstruct import sizeof, typedef import io import os from pathlib import Path +from cstruct.exceptions import ParserError MBR_DATA = (Path(__file__).parent.parent / 'mbr').read_bytes() @@ -255,3 +257,11 @@ def test_null_compare(): c = Dummy() assert c is not None assert c != None + + +def test_invalid_inline(): + with pytest.raises(ParserError): + cstruct.MemCStruct.parse( + 'struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN + ) + assert False diff --git a/tests/test_memcstruct.py b/tests/test_memcstruct.py index 7ea92c3..b312d43 100644 --- a/tests/test_memcstruct.py +++ b/tests/test_memcstruct.py @@ -25,10 +25,12 @@ # # ***************************************************************************** +import pytest import cstruct from cstruct import sizeof, typedef import os from pathlib import Path +from cstruct.exceptions import ParserError MBR_DATA = (Path(__file__).parent.parent / 'mbr').read_bytes() @@ -249,3 +251,11 @@ def test_null_compare(): c = Dummy() assert c is not None assert c != None + + +def test_invalid_inline(): + with pytest.raises(ParserError): + cstruct.MemCStruct.parse( + 'struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN + ) + assert False From 2ebb795cafcc37bc91e5aba9e511807780e621bb Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 13:16:02 +0000 Subject: [PATCH 15/78] Add anonymous nested union --- cstruct/c_parser.py | 11 +++ tests/test_nested.py | 212 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 tests/test_nested.py diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index e4cfbb2..34229bc 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -182,6 +182,7 @@ def parse_struct( flexible_array: bool = False offset: int = 0 max_alignment: int = 0 + anonymous: int = 0 if isinstance(__struct__, Tokens): tokens = __struct__ else: @@ -197,6 +198,16 @@ def parse_struct( vname = tokens.pop() if vname in fields_types: raise ParserError("Duplicate member '{}'".format(vname)) + # anonymous nested union + if vname == ';' and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): + # add the anonymous struct fields to the parent + for nested_field_name, nested_field_type in field_type.ref.__fields_types__.items(): + if nested_field_name in fields_types: + raise ParserError("Duplicate member '{}'".format(nested_field_name)) + fields_types[nested_field_name] = nested_field_type + vname = "__anonymous{}".format(anonymous) + anonymous += 1 + tokens.push(';') fields_types[vname] = field_type # calculate the max field size (for the alignment) max_alignment = max(max_alignment, field_type.alignment) diff --git a/tests/test_nested.py b/tests/test_nested.py new file mode 100644 index 0000000..4220ca5 --- /dev/null +++ b/tests/test_nested.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# ***************************************************************************** +# +# Copyright (c) 2013-2019 Andrea Bonomi +# +# Published under the terms of the MIT license. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# ***************************************************************************** + +import pytest +import cstruct +from cstruct import sizeof +from cstruct.exceptions import ParserError + + +INVALID_ANONYMOUS = """ + struct NestedStruct { + struct { + int a; + int b; + }; + int a; + int b; + }; +""" + + +class NestedStruct(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct NestedStruct { + struct { + int a; + int b; + } s; + int a; + int b; + }; + """ + + +class NestedUnion(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + union NestedUnion { + struct { + int a; + int b; + } s; + int a; + }; + """ + + +class NestedAnonymousUnion(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + union NestedAnonymousUnion { + struct { + int a; + int b; + }; + int c; + }; + """ + + +class Packet(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + #define MaxPacket 20 + + struct Packet { + uint8_t packetLength; + union { + uint8_t bytes[MaxPacket]; + struct { + uint16_t field1; + uint16_t field2; + uint16_t field3; + } format1; + struct { + double value1; + double value2; + } format2; + }; + }; + """ + + +def test_invalid_anonymous(): + with pytest.raises(ParserError): + cstruct.parse(INVALID_ANONYMOUS) + assert True + + +def test_sizeof_nested_struct(): + assert sizeof('struct NestedStruct') == 16 + o = NestedStruct() + assert len(o) == 16 + + +def test_pack_unpack_nested_struct(): + o = NestedStruct() + o.s.a = 1 + o.s.b = 2 + o.a = 10 + o.b = 20 + b = o.pack() + o1 = NestedStruct() + + o1.unpack(b) + assert o1.s.a == 1 + assert o1.s.b == 2 + assert o1.a == 10 + assert o1.b == 20 + + +def test_sizeof_nested_union(): + assert sizeof('struct NestedUnion') == 8 + o = NestedUnion() + assert len(o) == 8 + + +def test_pack_unpack_nested_union(): + o = NestedUnion() + o.s.a = 1 + o.s.b = 2 + b = o.pack() + + o1 = NestedUnion() + o1.unpack(b) + assert o1.s.a == 1 + assert o1.s.b == 2 + + o = NestedUnion() + o.a = 1979 + b = o.pack() + + o1 = NestedUnion() + o1.unpack(b) + assert o1.a == 1979 + o1.s.b = 0 + assert o1.a == 1979 + o1.s.a = 0 + assert o1.a == 0 + + +def test_sizeof_nested_anonymous_union(): + assert sizeof('struct NestedAnonymousUnion') == 8 + o = NestedAnonymousUnion() + assert len(o) == 8 + + +def test_pack_unpack_nested_anonymous_union(): + o = NestedAnonymousUnion() + o.a = 1 + o.b = 2 + b = o.pack() + + o1 = NestedAnonymousUnion() + o1.unpack(b) + assert o1.a == 1 + assert o1.b == 2 + + o = NestedAnonymousUnion() + o.c = 1979 + b = o.pack() + + o1 = NestedAnonymousUnion() + o1.unpack(b) + assert o1.c == 1979 + o1.b = 0 + assert o1.c == 1979 + o1.a = 0 + assert o1.c == 0 + + +def test_nested_anonymous_union_struct(): + o = Packet() + assert sizeof("struct Packet") == len(o) + + o = Packet() + o.packetLength = 10 + o.format1.field1 = 11 + o.format1.field2 = 12 + o.format1.field3 = 13 + b = o.pack() + + o1 = Packet() + o1.unpack(b) + assert o1.format1.field1 == 11 + assert o1.format1.field2 == 12 + assert o1.format1.field3 == 13 From 221238967a49133463ef11b2cb79928af510421f Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 13:19:44 +0000 Subject: [PATCH 16/78] version 4.0 --- changelog.txt | 12 ++++++++++++ cstruct/__init__.py | 2 +- setup.py | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index aaba19e..65b3659 100644 --- a/changelog.txt +++ b/changelog.txt @@ -163,3 +163,15 @@ ### Fix - Fix padding tests on 32bit architectures + +### 4.0 + +2022-11-01 + +### Added + +- Add support for nameless inline struct + +### Improved + +- Python 3.11 support diff --git a/cstruct/__init__.py b/cstruct/__init__.py index d20062e..e1ad18f 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = 'Andrea Bonomi ' __license__ = 'MIT' -__version__ = '3.3' +__version__ = '4.0' __date__ = '15 August 2013' import struct diff --git a/setup.py b/setup.py index f8814ff..33bf4e5 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ def readme(): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ], keywords='struct', author='Andrea Bonomi', From eec2cd35d1334f10110b10d61da67a990a953435 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Mon, 31 Oct 2022 19:54:34 +0100 Subject: [PATCH 17/78] Feature: Introduce C enum parsing using the CEnum class one create Python Enums easily using a C style syntax. ``` class Test(CEnum): __enum__ = """ A = 1, B = 2, C """ ``` Signed-off-by: Sophie Tyalie --- cstruct/__init__.py | 1 + cstruct/abstract.py | 76 ++++++++++++++++++++++++++++++++++++++++++++- cstruct/c_parser.py | 55 ++++++++++++++++++++++++++++++-- cstruct/cenum.py | 4 +++ tests/test_cenum.py | 17 ++++++++++ 5 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 cstruct/cenum.py create mode 100644 tests/test_cenum.py diff --git a/cstruct/__init__.py b/cstruct/__init__.py index e1ad18f..d2331ce 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -43,6 +43,7 @@ from .cstruct import CStruct from .c_parser import parse_def from .mem_cstruct import MemCStruct +from .cenum import CEnum __all__ = [ 'LITTLE_ENDIAN', diff --git a/cstruct/abstract.py b/cstruct/abstract.py index f5aeca9..0843bd3 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -28,8 +28,10 @@ from collections import OrderedDict from typing import Any, BinaryIO, List, Dict, Optional, Type, Tuple, Union import hashlib +from enum import IntEnum, EnumMeta, _EnumDict +from unicodedata import name from .base import STRUCTS -from .c_parser import parse_struct, parse_def, Tokens +from .c_parser import parse_struct, parse_def, parse_enum, Tokens from .field import calculate_padding, FieldType from .exceptions import CStructException @@ -257,3 +259,75 @@ def __setstate__(self, state: bytes) -> bool: state: bytes to be unpacked """ return self.unpack(state) + + +class CEnumMeta(EnumMeta): + __size__: int = 0 + + class WrapperDict(_EnumDict): + def __setitem__(self, key: str, value: Any) -> None: + if key == "__enum__": + env = parse_enum(value, self["__qualname__"]) + for k, v in env["__constants__"].items(): + super().__setitem__(k, v) + else: + return super().__setitem__(key, value) + + @classmethod + def __prepare__(metacls, cls, bases, **kwds): + namespace = EnumMeta.__prepare__(cls, bases, **kwds) + namespace.__class__ = metacls.WrapperDict + return namespace + + def __len__(cls) -> int: + "Enum size (in bytes)" + return cls.__size__ + + @property + def size(cls) -> int: + "Enum size (in bytes)" + return cls.__size__ + +class AbstractCEnum(IntEnum, metaclass=CEnumMeta): + """ + Abstract C enum to Python class + """ + + @classmethod + def parse( + cls, + __enum__: Union[str, Tokens, Dict[str, Any]], + __name__: Optional[str] = None, + __size__: Optional[int] = None, + **kargs: Dict[str, Any] + ) -> Type["AbstractCEnum"]: + """ + Return a new Python Enum class mapping a C enum definition + + Args: + __enum__: Definition of the enum in C syntax + __name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated + + Returns: + cls: A new class mapping the definition + """ + + cls_kargs: Dict[str, Any] = dict(kargs) + if __size__ is not None: + cls_kargs['__size__'] = __size__ + + cls_kargs['__enum__'] = __enum__ + if isinstance(__enum__, (str, Tokens)): + del cls_kargs["__enum__"] + cls_kargs.update(parse_def(__enum__, __cls__=cls, **cls_kargs)) + cls_kargs["__enum__"] = None + elif isinstance(__enum__, dict): + del cls_kargs["__enum__"] + cls_kargs.update(__enum__) + cls_kargs["__enum__"] = None + if __name__ is None: + __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest() + cls_kargs["__anonymous__"] = True + cls_kargs["__name__"] = __name__ + return IntEnum(__name__, cls_kargs["__constants__"]) + return type(__name__, (cls,), cls_kargs) \ No newline at end of file diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 34229bc..cfc6521 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -31,7 +31,7 @@ from .exceptions import CStructException, ParserError if TYPE_CHECKING: - from .abstract import AbstractCStruct + from .abstract import AbstractCStruct, AbstractCEnum __all__ = ['parse_struct', 'parse_def', 'Tokens'] @@ -52,7 +52,7 @@ def __init__(self, text: str) -> None: else: lines.append(line) text = " ".join(lines) - text = text.replace(";", " ; ").replace("{", " { ").replace("}", " } ") + text = text.replace(";", " ; ").replace("{", " { ").replace("}", " } ").replace(",", " , ").replace("=", " = ") self.tokens = text.split() def pop(self) -> str: @@ -168,6 +168,57 @@ def parse_def( else: raise ParserError("{} definition expected".format(vtype)) +def parse_enum( + __enum__: Union[str, Tokens], + __cls__: Type['AbstractCEnum'], + **kargs: Any +) -> Optional[Dict[str, Any]]: + constants: Dict[str, int] = OrderedDict() + + if isinstance(__enum__, Tokens): + tokens = __enum__ + else: + tokens = Tokens(__enum__) + + while len(tokens): + name = tokens.pop() + + next_token = tokens.pop() + if next_token == ",": # enum-constant without explicit value + if len(constants) == 0: + value = 0 + else: + value = next(reversed(constants.values())) + 1 + elif next_token == "=": + exp_elems = [] + next_token = tokens.pop() + while not next_token.endswith(","): + exp_elems.append(next_token) + if len(tokens) > 0: + next_token = tokens.pop() + else: + break + + if len(exp_elems) == 0: + raise ParserError(f"enum is missing value expression") + + int_expr = " ".join(exp_elems) + try: + value = c_eval(int_expr) + except (ValueError, TypeError): + value = int(int_expr) + else: + raise ParserError(f"{__enum__} is not a valid enum expression") + + if name in constants: + raise ParserError(f"duplicate enum name {name}") + constants[name] = value + + result = { + '__constants__': constants + } + return result + def parse_struct( __struct__: Union[str, Tokens], diff --git a/cstruct/cenum.py b/cstruct/cenum.py new file mode 100644 index 0000000..0fc1654 --- /dev/null +++ b/cstruct/cenum.py @@ -0,0 +1,4 @@ +from .abstract import AbstractCEnum + +class CEnum(AbstractCEnum): + ... \ No newline at end of file diff --git a/tests/test_cenum.py b/tests/test_cenum.py new file mode 100644 index 0000000..f137f8f --- /dev/null +++ b/tests/test_cenum.py @@ -0,0 +1,17 @@ +from cstruct import CEnum + +class Dummy(CEnum): + __enum__ = """ + A, + B, + C = 2, + D = 5 + 7, + E = 2 + """ + +def test_dummy(): + assert Dummy.A == 0 + assert Dummy.B == 1 + assert Dummy.C == 2 + assert Dummy.D == 12 + assert Dummy.E == 2 \ No newline at end of file From c53ac8186d3a6000d4844e12c0f0e025d9f524e9 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Mon, 31 Oct 2022 20:13:44 +0100 Subject: [PATCH 18/78] Feature: Allow CEnum to be initialized using __def__ - renamed `parse_def` to `parse_struct_def` to allow for `parse_enum_def` function Signed-off-by: Sophie Tyalie --- cstruct/__init__.py | 4 ++-- cstruct/abstract.py | 16 ++++++++++------ cstruct/c_parser.py | 40 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index d2331ce..c7ccb12 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -41,7 +41,7 @@ ) from .abstract import CStructMeta, AbstractCStruct from .cstruct import CStruct -from .c_parser import parse_def +from .c_parser import parse_struct_def from .mem_cstruct import MemCStruct from .cenum import CEnum @@ -151,7 +151,7 @@ def parse( """ if __cls__ is None: __cls__ = CStruct - cls_def = parse_def(__struct__, __cls__=__cls__, **kargs) + cls_def = parse_struct_def(__struct__, __cls__=__cls__, **kargs) if cls_def is None: return None else: diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 0843bd3..b997f9c 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -31,7 +31,7 @@ from enum import IntEnum, EnumMeta, _EnumDict from unicodedata import name from .base import STRUCTS -from .c_parser import parse_struct, parse_def, parse_enum, Tokens +from .c_parser import parse_struct, parse_struct_def, parse_enum_def,parse_enum, Tokens from .field import calculate_padding, FieldType from .exceptions import CStructException @@ -50,7 +50,7 @@ def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[s namespace.update(parse_struct(**namespace)) __struct__ = True if '__def__' in namespace: - namespace.update(parse_def(**namespace)) + namespace.update(parse_struct_def(**namespace)) __struct__ = True # Create the new class new_class: Type[Any] = super().__new__(metacls, name, bases, namespace) @@ -132,7 +132,7 @@ def parse( cls_kargs['__struct__'] = __struct__ if isinstance(__struct__, (str, Tokens)): del cls_kargs['__struct__'] - cls_kargs.update(parse_def(__struct__, __cls__=cls, **cls_kargs)) + cls_kargs.update(parse_struct_def(__struct__, __cls__=cls, **cls_kargs)) cls_kargs['__struct__'] = None elif isinstance(__struct__, dict): del cls_kargs['__struct__'] @@ -266,8 +266,13 @@ class CEnumMeta(EnumMeta): class WrapperDict(_EnumDict): def __setitem__(self, key: str, value: Any) -> None: + env = None if key == "__enum__": env = parse_enum(value, self["__qualname__"]) + elif key == "__def__": + env = parse_enum_def(value, self["__qualname__"]) + + if env is not None: for k, v in env["__constants__"].items(): super().__setitem__(k, v) else: @@ -319,7 +324,7 @@ def parse( cls_kargs['__enum__'] = __enum__ if isinstance(__enum__, (str, Tokens)): del cls_kargs["__enum__"] - cls_kargs.update(parse_def(__enum__, __cls__=cls, **cls_kargs)) + cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs)) cls_kargs["__enum__"] = None elif isinstance(__enum__, dict): del cls_kargs["__enum__"] @@ -329,5 +334,4 @@ def parse( __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest() cls_kargs["__anonymous__"] = True cls_kargs["__name__"] = __name__ - return IntEnum(__name__, cls_kargs["__constants__"]) - return type(__name__, (cls,), cls_kargs) \ No newline at end of file + return IntEnum(__name__, cls_kargs["__constants__"]) \ No newline at end of file diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index cfc6521..dfb74d1 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: from .abstract import AbstractCStruct, AbstractCEnum -__all__ = ['parse_struct', 'parse_def', 'Tokens'] +__all__ = ['parse_struct', 'parse_struct_def', 'Tokens'] class Tokens(object): @@ -142,7 +142,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt return FieldType(kind, c_type, ref, vlen, flexible_array, byte_order, offset) -def parse_def( +def parse_struct_def( __def__: Union[str, Tokens], __cls__: Type['AbstractCStruct'], __byte_order__: Optional[str] = None, @@ -168,6 +168,33 @@ def parse_def( else: raise ParserError("{} definition expected".format(vtype)) + +def parse_enum_def( + __def__: Union[str, Tokens], + __cls__: Type["AbstractCEnum"], + **kargs: Any +) -> Optional[Dict[str, Any]]: + # naive C enum parsing + if isinstance(__def__, Tokens): + tokens = __def__ + else: + tokens = Tokens(__def__) + if not tokens: + return None + kind = tokens.pop() + if kind not in ['enum']: + raise ParserError(f"enum expected - {kind}") + + vtype = tokens.pop() + if tokens.get() == '{': # named enum + tokens.pop() + return parse_enum(tokens, __cls__=__cls__) + elif vtype == '{': + return parse_enum(tokens, __cls__=__cls__) + else: + raise ParserError(f"{vtype} definition expected") + + def parse_enum( __enum__: Union[str, Tokens], __cls__: Type['AbstractCEnum'], @@ -181,6 +208,10 @@ def parse_enum( tokens = Tokens(__enum__) while len(tokens): + if tokens.get() == '}': + tokens.pop() + break + name = tokens.pop() next_token = tokens.pop() @@ -192,7 +223,7 @@ def parse_enum( elif next_token == "=": exp_elems = [] next_token = tokens.pop() - while not next_token.endswith(","): + while next_token not in {",", "}"}: exp_elems.append(next_token) if len(tokens) > 0: next_token = tokens.pop() @@ -207,6 +238,9 @@ def parse_enum( value = c_eval(int_expr) except (ValueError, TypeError): value = int(int_expr) + + if next_token == "}": + break else: raise ParserError(f"{__enum__} is not a valid enum expression") From 2aa934e13ab08458a2e94a0e1a6fb391e7d035f1 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 11:18:31 +0100 Subject: [PATCH 19/78] enum: Register enums in base.ENUMS Using Pythons metaclass new function, we look for valid C Enum classes (__enum__ = True) and register them there in the `base.ENUMS` variable - `__size__` is a required parameter for Enums - introduced `CEnumException` Signed-off-by: Sophie Tyalie --- cstruct/abstract.py | 21 +++++++++++++++++---- cstruct/base.py | 4 +++- cstruct/exceptions.py | 3 +++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index b997f9c..bf88060 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -28,12 +28,13 @@ from collections import OrderedDict from typing import Any, BinaryIO, List, Dict, Optional, Type, Tuple, Union import hashlib +import sys from enum import IntEnum, EnumMeta, _EnumDict from unicodedata import name -from .base import STRUCTS +from .base import STRUCTS, ENUMS from .c_parser import parse_struct, parse_struct_def, parse_enum_def,parse_enum, Tokens from .field import calculate_padding, FieldType -from .exceptions import CStructException +from .exceptions import CStructException, CEnumException __all__ = ['CStructMeta', 'AbstractCStruct'] @@ -262,8 +263,6 @@ def __setstate__(self, state: bytes) -> bool: class CEnumMeta(EnumMeta): - __size__: int = 0 - class WrapperDict(_EnumDict): def __setitem__(self, key: str, value: Any) -> None: env = None @@ -273,8 +272,13 @@ def __setitem__(self, key: str, value: Any) -> None: env = parse_enum_def(value, self["__qualname__"]) if env is not None: + # register the enum constants in the object namespace, + # using the Python Enum class Namespace dict that does the + # heavy lifting for k, v in env["__constants__"].items(): super().__setitem__(k, v) + + super().__setitem__("__enum__", True) else: return super().__setitem__(key, value) @@ -284,6 +288,15 @@ def __prepare__(metacls, cls, bases, **kwds): namespace.__class__ = metacls.WrapperDict return namespace + def __new__(metacls: type["CEnumMeta"], cls: str, bases: tuple[type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta": + inst = super().__new__(metacls, cls, bases, classdict, **kwds) + + if classdict.get("__enum__", False): + if "__size__" not in classdict: + raise CEnumException("__size__ not specified. Cannot derive size as it is architecture dependent") + ENUMS[cls] = inst + return inst + def __len__(cls) -> int: "Enum size (in bytes)" return cls.__size__ diff --git a/cstruct/base.py b/cstruct/base.py index b350a67..f9ca0f1 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -25,7 +25,7 @@ from typing import Any, Dict, Type, TYPE_CHECKING if TYPE_CHECKING: - from .abstract import AbstractCStruct + from .abstract import AbstractCStruct, AbstractCEnum __all__ = [ 'LITTLE_ENDIAN', @@ -48,6 +48,8 @@ STRUCTS: Dict[str, Type["AbstractCStruct"]] = {} +ENUMS: Dict[str, Type["AbstractCEnum"]] = {} + DEFINES: Dict[str, Any] = {} TYPEDEFS: Dict[str, str] = { diff --git a/cstruct/exceptions.py b/cstruct/exceptions.py index 65cd668..3d66fb3 100644 --- a/cstruct/exceptions.py +++ b/cstruct/exceptions.py @@ -23,11 +23,14 @@ # __all__ = [ + "CEnumException", "CStructException", "ParserError", "EvalError", ] +class CEnumException(Exception): + pass class CStructException(Exception): pass From 4a584122fb4435deb4bc51dfc4e9fb6a41139fc0 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 11:24:15 +0100 Subject: [PATCH 20/78] Removed __cls__ parameter in enum parsing It wasn't used so far and I'm not sure if that will change. Enum parsing is far more simple than struct parsing in this regard Signed-off-by: Sophie Tyalie --- cstruct/abstract.py | 4 ++-- cstruct/c_parser.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index bf88060..8c17523 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -267,9 +267,9 @@ class WrapperDict(_EnumDict): def __setitem__(self, key: str, value: Any) -> None: env = None if key == "__enum__": - env = parse_enum(value, self["__qualname__"]) + env = parse_enum(value) elif key == "__def__": - env = parse_enum_def(value, self["__qualname__"]) + env = parse_enum_def(value) if env is not None: # register the enum constants in the object namespace, diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index dfb74d1..bd63859 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -171,7 +171,6 @@ def parse_struct_def( def parse_enum_def( __def__: Union[str, Tokens], - __cls__: Type["AbstractCEnum"], **kargs: Any ) -> Optional[Dict[str, Any]]: # naive C enum parsing @@ -188,16 +187,15 @@ def parse_enum_def( vtype = tokens.pop() if tokens.get() == '{': # named enum tokens.pop() - return parse_enum(tokens, __cls__=__cls__) + return parse_enum(tokens) elif vtype == '{': - return parse_enum(tokens, __cls__=__cls__) + return parse_enum(tokens) else: raise ParserError(f"{vtype} definition expected") def parse_enum( __enum__: Union[str, Tokens], - __cls__: Type['AbstractCEnum'], **kargs: Any ) -> Optional[Dict[str, Any]]: constants: Dict[str, int] = OrderedDict() From 2c050517bf7d17cdac3a8d85115c74eae2257b92 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 11:46:17 +0100 Subject: [PATCH 21/78] Enum: Allow parsing of enums in struct definitions Previously defined named CEnums are correctly parsed when used in a struct definition now. It is unclear whether unnamed and `AbstractCEnum.parse` enums can also be used. Presumably not. Signed-off-by: Sophie Tyalie --- cstruct/base.py | 7 +++++++ cstruct/c_parser.py | 11 +++++++++-- cstruct/field.py | 24 ++++++++++++++++++++---- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/cstruct/base.py b/cstruct/base.py index f9ca0f1..ab21666 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -93,4 +93,11 @@ 'uint64': 'Q', } +ENUM_SIZE_TO_C_TYPE: Dict[int, str] = { + 1: 'uint8', + 2: 'uint16', + 4: 'uint32', + 8: 'uint64' +} + CHAR_ZERO = bytes('\0', 'ascii') diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index bd63859..bd28e3c 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -25,7 +25,7 @@ import re from collections import OrderedDict from typing import Union, Optional, Any, Dict, Type, TYPE_CHECKING -from .base import DEFINES, TYPEDEFS, STRUCTS +from .base import DEFINES, ENUMS, TYPEDEFS, STRUCTS from .field import calculate_padding, Kind, FieldType from .c_expr import c_eval from .exceptions import CStructException, ParserError @@ -76,7 +76,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt raise ParserError("Parsing error") c_type = tokens.pop() # signed/unsigned/struct - if c_type in ['signed', 'unsigned', 'struct', 'union'] and len(tokens) > 1: + if c_type in ['signed', 'unsigned', 'struct', 'union', 'enum'] and len(tokens) > 1: c_type = c_type + " " + tokens.pop() vlen = 1 @@ -136,6 +136,13 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt ref = STRUCTS[tail] except KeyError: raise ParserError("Unknow {} {}".format(c_type, tail)) + elif c_type.startswith('enum'): + c_type, tail = c_type.split(' ', 1) + kind = Kind.ENUM + try: + ref = ENUMS[tail] + except KeyError: + raise ParserError(f"Unknown '{c_type}' '{tail}'") else: # other types kind = Kind.NATIVE ref = None diff --git a/cstruct/field.py b/cstruct/field.py index 287a027..7e0e3e8 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -26,7 +26,7 @@ import struct from enum import Enum from typing import Optional, Any, List, Type, TYPE_CHECKING -from .base import NATIVE_ORDER, C_TYPE_TO_FORMAT +from .base import NATIVE_ORDER, C_TYPE_TO_FORMAT, ENUM_SIZE_TO_C_TYPE from .exceptions import ParserError if TYPE_CHECKING: @@ -58,6 +58,8 @@ class Kind(Enum): "Struct type" UNION = 2 "Union type" + ENUM = 3 + "Enum type" class FieldType(object): @@ -115,8 +117,12 @@ def unpack_from(self, buffer: bytes, offset: int = 0) -> Any: Returns: data: The unpacked data """ - if self.is_native: + if self.is_native or self.is_enum: result = struct.unpack_from(self.fmt, buffer, self.offset + offset) + + if self.is_enum: + result = tuple(map(self.ref, result)) + if self.is_array: return list(result) else: @@ -162,6 +168,11 @@ def is_native(self) -> bool: "True if the field is a native type (e.g. int, char)" return self.kind == Kind.NATIVE + @property + def is_enum(self) -> bool: + "True if the field is an enum" + return self.kind == Kind.ENUM + @property def is_struct(self) -> bool: "True if the field is a struct" @@ -180,13 +191,18 @@ def native_format(self) -> str: return C_TYPE_TO_FORMAT[self.c_type] except KeyError: raise ParserError("Unknow type {}".format(self.c_type)) + elif self.is_enum: + try: + return C_TYPE_TO_FORMAT[ENUM_SIZE_TO_C_TYPE[len(self.ref)]] + except KeyError: + raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}") else: return 'c' @property def fmt(self) -> str: "Field format prefixed by byte order (struct library format)" - if self.is_native: + if self.is_native or self.is_enum: fmt = (str(self.vlen) if self.vlen > 1 or self.flexible_array else '') + self.native_format else: # Struct/Union fmt = str(self.vlen * self.ref.sizeof()) + self.native_format @@ -203,7 +219,7 @@ def vsize(self) -> int: @property def alignment(self) -> int: "Alignment" - if self.is_native: + if self.is_native or self.is_enum: if self.byte_order is not None: return struct.calcsize(self.byte_order + self.native_format) else: From 64b0523495707939337d09ccd5c91893a8aff9ef Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 11:57:48 +0100 Subject: [PATCH 22/78] Enum: Fix parser Parser couldn't handle the case that an enum-constant without explicit value and no following `,` was the last thing in the enum definition. Example error case ``` enum { A } ``` Signed-off-by: Sophie Tyalie --- cstruct/c_parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index bd28e3c..7001b32 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -220,7 +220,8 @@ def parse_enum( name = tokens.pop() next_token = tokens.pop() - if next_token == ",": # enum-constant without explicit value + print(name, next_token) + if next_token in {",", "}"}: # enum-constant without explicit value if len(constants) == 0: value = 0 else: From b3bc43c5cb1d6ac638d25d23b2963f6b29814bbd Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 12:23:14 +0100 Subject: [PATCH 23/78] Enum: Fix `AbstractCEnum.parse` This function was not in a working state previously. While making the function work a few things needed to be changed too. For example should `len(enum)` return the number of elements instead of the size for CEnums and such. Introduced default enum size of 4 bytes Signed-off-by: Sophie Tyalie --- cstruct/abstract.py | 29 ++++++++++------------------- cstruct/base.py | 1 + cstruct/field.py | 2 +- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 8c17523..616acb5 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -31,12 +31,12 @@ import sys from enum import IntEnum, EnumMeta, _EnumDict from unicodedata import name -from .base import STRUCTS, ENUMS +from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE from .c_parser import parse_struct, parse_struct_def, parse_enum_def,parse_enum, Tokens from .field import calculate_padding, FieldType from .exceptions import CStructException, CEnumException -__all__ = ['CStructMeta', 'AbstractCStruct'] +__all__ = ['CStructMeta', 'AbstractCStruct', "CEnumMeta", 'AbstractCEnum'] class CStructMeta(ABCMeta): @@ -277,8 +277,6 @@ def __setitem__(self, key: str, value: Any) -> None: # heavy lifting for k, v in env["__constants__"].items(): super().__setitem__(k, v) - - super().__setitem__("__enum__", True) else: return super().__setitem__(key, value) @@ -291,16 +289,13 @@ def __prepare__(metacls, cls, bases, **kwds): def __new__(metacls: type["CEnumMeta"], cls: str, bases: tuple[type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta": inst = super().__new__(metacls, cls, bases, classdict, **kwds) - if classdict.get("__enum__", False): + if len(inst) > 0: if "__size__" not in classdict: raise CEnumException("__size__ not specified. Cannot derive size as it is architecture dependent") - ENUMS[cls] = inst + if not classdict.get("__anonymous__", False): + ENUMS[cls] = inst return inst - def __len__(cls) -> int: - "Enum size (in bytes)" - return cls.__size__ - @property def size(cls) -> int: "Enum size (in bytes)" @@ -331,20 +326,16 @@ def parse( """ cls_kargs: Dict[str, Any] = dict(kargs) - if __size__ is not None: - cls_kargs['__size__'] = __size__ + cls_kargs['__size__'] = DEFAULT_ENUM_SIZE if __size__ is None else __size__ - cls_kargs['__enum__'] = __enum__ if isinstance(__enum__, (str, Tokens)): - del cls_kargs["__enum__"] cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs)) - cls_kargs["__enum__"] = None elif isinstance(__enum__, dict): - del cls_kargs["__enum__"] cls_kargs.update(__enum__) - cls_kargs["__enum__"] = None + if __name__ is None: __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest() cls_kargs["__anonymous__"] = True - cls_kargs["__name__"] = __name__ - return IntEnum(__name__, cls_kargs["__constants__"]) \ No newline at end of file + + cls_kargs.update(cls_kargs["__constants__"]) + return cls(__name__, cls_kargs) \ No newline at end of file diff --git a/cstruct/base.py b/cstruct/base.py index ab21666..74e569f 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -101,3 +101,4 @@ } CHAR_ZERO = bytes('\0', 'ascii') +DEFAULT_ENUM_SIZE = 4 \ No newline at end of file diff --git a/cstruct/field.py b/cstruct/field.py index 7e0e3e8..08c5bc0 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -193,7 +193,7 @@ def native_format(self) -> str: raise ParserError("Unknow type {}".format(self.c_type)) elif self.is_enum: try: - return C_TYPE_TO_FORMAT[ENUM_SIZE_TO_C_TYPE[len(self.ref)]] + return C_TYPE_TO_FORMAT[ENUM_SIZE_TO_C_TYPE[self.ref.size]] except KeyError: raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}") else: From 4974d951e3408aec5ab9c410a60021ca9266ad04 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 12:26:56 +0100 Subject: [PATCH 24/78] Enum: Remove hard size enforcement A warning will now be displayed and the enum size will be set to `base.DEFAULT_ENUM_SIZE` instead Signed-off-by: Sophie Tyalie --- cstruct/abstract.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 616acb5..f8ccaff 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -291,7 +291,8 @@ def __new__(metacls: type["CEnumMeta"], cls: str, bases: tuple[type, ...], class if len(inst) > 0: if "__size__" not in classdict: - raise CEnumException("__size__ not specified. Cannot derive size as it is architecture dependent") + inst.__size__ = DEFAULT_ENUM_SIZE + print(f"Warning: __size__ not specified for enum {cls}. Will default to {DEFAULT_ENUM_SIZE} bytes") if not classdict.get("__anonymous__", False): ENUMS[cls] = inst return inst @@ -320,13 +321,15 @@ def parse( Args: __enum__: Definition of the enum in C syntax __name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated + __size__: Number of bytes that the enum should be read as Returns: cls: A new class mapping the definition """ cls_kargs: Dict[str, Any] = dict(kargs) - cls_kargs['__size__'] = DEFAULT_ENUM_SIZE if __size__ is None else __size__ + if __size__ is not None: + cls_kargs['__size__'] = __size__ if isinstance(__enum__, (str, Tokens)): cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs)) From f44f7109cf746ad033d912b4995382dc034b1094 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 12:42:35 +0100 Subject: [PATCH 25/78] Enum: Allow inline definition of enums This includes named and unnamed enums that are defined in a struct, similar to this: ``` struct { enum {A, B, C} v; } ``` Signed-off-by: Sophie Tyalie --- cstruct/c_parser.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 7001b32..3e4eaab 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -137,12 +137,24 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt except KeyError: raise ParserError("Unknow {} {}".format(c_type, tail)) elif c_type.startswith('enum'): + #from .abstract import AbstractCEnum + from .cenum import CEnum + c_type, tail = c_type.split(' ', 1) kind = Kind.ENUM - try: - ref = ENUMS[tail] - except KeyError: - raise ParserError(f"Unknown '{c_type}' '{tail}'") + if tokens.get() == '{': # Named nested struct + tokens.push(tail) + tokens.push(c_type) + ref = CEnum.parse(tokens, __name__=tail) + elif tail == '{': # unnamed nested struct + tokens.push(tail) + tokens.push(c_type) + ref = CEnum.parse(tokens) + else: + try: + ref = ENUMS[tail] + except KeyError: + raise ParserError(f"Unknown '{c_type}' '{tail}'") else: # other types kind = Kind.NATIVE ref = None @@ -254,6 +266,9 @@ def parse_enum( raise ParserError(f"duplicate enum name {name}") constants[name] = value + if next_token == "}": + break + result = { '__constants__': constants } From 2e9836c0ba516bbeb183a9356dd48c4eff60ea8b Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 12:47:01 +0100 Subject: [PATCH 26/78] Enum: Fix enum parser I'm not fully sure what the test case was that would have resulted in an error, but looking over the code there was a break at the wrong position that could have resulted in an unparsed enum constant. Signed-off-by: Sophie Tyalie --- cstruct/c_parser.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 3e4eaab..1ead448 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -232,13 +232,12 @@ def parse_enum( name = tokens.pop() next_token = tokens.pop() - print(name, next_token) if next_token in {",", "}"}: # enum-constant without explicit value if len(constants) == 0: value = 0 else: value = next(reversed(constants.values())) + 1 - elif next_token == "=": + elif next_token == "=": # enum-constant with explicit value exp_elems = [] next_token = tokens.pop() while next_token not in {",", "}"}: @@ -256,9 +255,6 @@ def parse_enum( value = c_eval(int_expr) except (ValueError, TypeError): value = int(int_expr) - - if next_token == "}": - break else: raise ParserError(f"{__enum__} is not a valid enum expression") From 942b62cdf6a54361595b78a9a3ce898e1368d918 Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 13:09:21 +0100 Subject: [PATCH 27/78] Enum: use signed instead of unsigned int for enum representation This hits upon a larger issue. It doesn't seem fully clear what type a compiler will use to represent an enum, but signed integer seems to be a good enough solution as enum-constants (not the enum itself) are defined as int signed. See following patch note https://gcc.gnu.org/legacy-ml/gcc-patches/2000-07/msg00993.html Signed-off-by: Sophie Tyalie --- cstruct/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cstruct/base.py b/cstruct/base.py index 74e569f..6b427ae 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -94,10 +94,10 @@ } ENUM_SIZE_TO_C_TYPE: Dict[int, str] = { - 1: 'uint8', - 2: 'uint16', - 4: 'uint32', - 8: 'uint64' + 1: 'int8', + 2: 'int16', + 4: 'int32', + 8: 'int64' } CHAR_ZERO = bytes('\0', 'ascii') From 3b5bc778b682503397d187f4aa9f31517a53144d Mon Sep 17 00:00:00 2001 From: Sophie Tyalie Date: Tue, 1 Nov 2022 13:14:22 +0100 Subject: [PATCH 28/78] Enum: Fix pytest errors with older python versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python 3.10 supports using `type[…]` or `tuple[…]` as type hints. This is relatively new and as such not support in older pythons Signed-off-by: Sophie Tyalie --- cstruct/abstract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index f8ccaff..88e8493 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -286,7 +286,7 @@ def __prepare__(metacls, cls, bases, **kwds): namespace.__class__ = metacls.WrapperDict return namespace - def __new__(metacls: type["CEnumMeta"], cls: str, bases: tuple[type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta": + def __new__(metacls: Type["CEnumMeta"], cls: str, bases: Tuple[Type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta": inst = super().__new__(metacls, cls, bases, classdict, **kwds) if len(inst) > 0: From e0a1c93cb9b06dd15bf1f2ef8bef7ad12e52ef68 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 13:31:39 +0000 Subject: [PATCH 29/78] black code style --- cstruct/abstract.py | 9 +++++---- cstruct/base.py | 9 ++------- cstruct/c_parser.py | 20 ++++++-------------- cstruct/cenum.py | 3 ++- cstruct/exceptions.py | 2 ++ tests/test_cenum.py | 14 ++++++++------ tests/test_cstruct.py | 4 +--- tests/test_define.py | 12 ++++++++---- tests/test_memcstruct.py | 4 +--- 9 files changed, 35 insertions(+), 42 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 88e8493..a7d9903 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -32,7 +32,7 @@ from enum import IntEnum, EnumMeta, _EnumDict from unicodedata import name from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE -from .c_parser import parse_struct, parse_struct_def, parse_enum_def,parse_enum, Tokens +from .c_parser import parse_struct, parse_struct_def, parse_enum_def, parse_enum, Tokens from .field import calculate_padding, FieldType from .exceptions import CStructException, CEnumException @@ -111,7 +111,7 @@ def parse( __name__: Optional[str] = None, __byte_order__: Optional[str] = None, __is_union__: Optional[bool] = False, - **kargs: Dict[str, Any] + **kargs: Dict[str, Any], ) -> Type["AbstractCStruct"]: """ Return a new class mapping a C struct/union definition. @@ -302,6 +302,7 @@ def size(cls) -> int: "Enum size (in bytes)" return cls.__size__ + class AbstractCEnum(IntEnum, metaclass=CEnumMeta): """ Abstract C enum to Python class @@ -313,7 +314,7 @@ def parse( __enum__: Union[str, Tokens, Dict[str, Any]], __name__: Optional[str] = None, __size__: Optional[int] = None, - **kargs: Dict[str, Any] + **kargs: Dict[str, Any], ) -> Type["AbstractCEnum"]: """ Return a new Python Enum class mapping a C enum definition @@ -341,4 +342,4 @@ def parse( cls_kargs["__anonymous__"] = True cls_kargs.update(cls_kargs["__constants__"]) - return cls(__name__, cls_kargs) \ No newline at end of file + return cls(__name__, cls_kargs) diff --git a/cstruct/base.py b/cstruct/base.py index 6b427ae..96f2496 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -93,12 +93,7 @@ 'uint64': 'Q', } -ENUM_SIZE_TO_C_TYPE: Dict[int, str] = { - 1: 'int8', - 2: 'int16', - 4: 'int32', - 8: 'int64' -} +ENUM_SIZE_TO_C_TYPE: Dict[int, str] = {1: 'int8', 2: 'int16', 4: 'int32', 8: 'int64'} CHAR_ZERO = bytes('\0', 'ascii') -DEFAULT_ENUM_SIZE = 4 \ No newline at end of file +DEFAULT_ENUM_SIZE = 4 diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 1ead448..3eb574b 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -137,7 +137,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt except KeyError: raise ParserError("Unknow {} {}".format(c_type, tail)) elif c_type.startswith('enum'): - #from .abstract import AbstractCEnum + # from .abstract import AbstractCEnum from .cenum import CEnum c_type, tail = c_type.split(' ', 1) @@ -165,7 +165,7 @@ def parse_struct_def( __def__: Union[str, Tokens], __cls__: Type['AbstractCStruct'], __byte_order__: Optional[str] = None, - **kargs: Any # Type['AbstractCStruct'], + **kargs: Any, # Type['AbstractCStruct'], ) -> Optional[Dict[str, Any]]: # naive C struct parsing if isinstance(__def__, Tokens): @@ -188,10 +188,7 @@ def parse_struct_def( raise ParserError("{} definition expected".format(vtype)) -def parse_enum_def( - __def__: Union[str, Tokens], - **kargs: Any -) -> Optional[Dict[str, Any]]: +def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, Any]]: # naive C enum parsing if isinstance(__def__, Tokens): tokens = __def__ @@ -213,10 +210,7 @@ def parse_enum_def( raise ParserError(f"{vtype} definition expected") -def parse_enum( - __enum__: Union[str, Tokens], - **kargs: Any -) -> Optional[Dict[str, Any]]: +def parse_enum(__enum__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, Any]]: constants: Dict[str, int] = OrderedDict() if isinstance(__enum__, Tokens): @@ -265,9 +259,7 @@ def parse_enum( if next_token == "}": break - result = { - '__constants__': constants - } + result = {'__constants__': constants} return result @@ -276,7 +268,7 @@ def parse_struct( __cls__: Type['AbstractCStruct'], __is_union__: bool = False, __byte_order__: Optional[str] = None, - **kargs: Any + **kargs: Any, ) -> Dict[str, Any]: # naive C struct parsing __is_union__ = bool(__is_union__) diff --git a/cstruct/cenum.py b/cstruct/cenum.py index 0fc1654..7929f03 100644 --- a/cstruct/cenum.py +++ b/cstruct/cenum.py @@ -1,4 +1,5 @@ from .abstract import AbstractCEnum + class CEnum(AbstractCEnum): - ... \ No newline at end of file + ... diff --git a/cstruct/exceptions.py b/cstruct/exceptions.py index 3d66fb3..b5546b4 100644 --- a/cstruct/exceptions.py +++ b/cstruct/exceptions.py @@ -29,9 +29,11 @@ "EvalError", ] + class CEnumException(Exception): pass + class CStructException(Exception): pass diff --git a/tests/test_cenum.py b/tests/test_cenum.py index f137f8f..c9184b9 100644 --- a/tests/test_cenum.py +++ b/tests/test_cenum.py @@ -1,7 +1,8 @@ from cstruct import CEnum + class Dummy(CEnum): - __enum__ = """ + __enum__ = """ A, B, C = 2, @@ -9,9 +10,10 @@ class Dummy(CEnum): E = 2 """ + def test_dummy(): - assert Dummy.A == 0 - assert Dummy.B == 1 - assert Dummy.C == 2 - assert Dummy.D == 12 - assert Dummy.E == 2 \ No newline at end of file + assert Dummy.A == 0 + assert Dummy.B == 1 + assert Dummy.C == 2 + assert Dummy.D == 12 + assert Dummy.E == 2 diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index b5eab9f..e591273 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -261,7 +261,5 @@ def test_null_compare(): def test_invalid_inline(): with pytest.raises(ParserError): - cstruct.MemCStruct.parse( - 'struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN - ) + cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) assert False diff --git a/tests/test_define.py b/tests/test_define.py index a47e3a8..eda2947 100644 --- a/tests/test_define.py +++ b/tests/test_define.py @@ -87,16 +87,20 @@ class Invalid(cstruct.CStruct): def test_invalid_define(): with pytest.raises(ParserError): - cstruct.parse(""" + cstruct.parse( + """ #define xxx yyy zzz - """) + """ + ) def test_invalid_struct(): with pytest.raises(ParserError): - cstruct.parse(""" + cstruct.parse( + """ struct { int a; int; } - """) + """ + ) diff --git a/tests/test_memcstruct.py b/tests/test_memcstruct.py index b312d43..0ff95f8 100644 --- a/tests/test_memcstruct.py +++ b/tests/test_memcstruct.py @@ -255,7 +255,5 @@ def test_null_compare(): def test_invalid_inline(): with pytest.raises(ParserError): - cstruct.MemCStruct.parse( - 'struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN - ) + cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) assert False From c58f158e75cc1b74ca4daf0e7d5199e8ca1ebdb2 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 18:09:52 +0000 Subject: [PATCH 30/78] Add enum support to cstruct.parse --- cstruct/__init__.py | 13 ++++++++----- cstruct/c_parser.py | 26 +++++++++++++++++--------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index c7ccb12..7015ae3 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -28,7 +28,7 @@ __date__ = '15 August 2013' import struct -from typing import Any, Dict, Optional, Type +from typing import Any, Dict, Optional, Type, Union from .base import ( LITTLE_ENDIAN, BIG_ENDIAN, @@ -39,7 +39,7 @@ C_TYPE_TO_FORMAT, CHAR_ZERO, ) -from .abstract import CStructMeta, AbstractCStruct +from .abstract import CStructMeta, AbstractCStruct, AbstractCEnum from .cstruct import CStruct from .c_parser import parse_struct_def from .mem_cstruct import MemCStruct @@ -52,6 +52,7 @@ 'CHAR_ZERO', 'CStruct', 'MemCStruct', + 'CEnum', 'define', 'undef', 'getdef', @@ -134,13 +135,13 @@ def sizeof(type_: str) -> int: def parse( __struct__: str, __cls__: Optional[Type[AbstractCStruct]] = None, __name__: Optional[str] = None, **kargs: Dict[str, Any] -) -> Optional[Type[AbstractCStruct]]: +) -> Union[Type[AbstractCStruct], Type[AbstractCEnum], None]: """ - Return a new class mapping a C struct/union definition. + Return a new class mapping a C struct/union/enum definition. If the string does not contains any definition, return None. Args: - __struct__ (str): definition of the struct (or union) in C syntax + __struct__ (str): definition of the struct (or union/enum) in C syntax __cls__ (type): super class - CStruct(default) or MemCStruct __name__ (str): name of the new class. If empty, a name based on the __struct__ hash is generated __byte_order__ (str): byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER @@ -154,5 +155,7 @@ def parse( cls_def = parse_struct_def(__struct__, __cls__=__cls__, **kargs) if cls_def is None: return None + elif cls_def['__is_enum__']: + return AbstractCEnum.parse(cls_def, __name__, **kargs) else: return __cls__.parse(cls_def, __name__, **kargs) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 3eb574b..f7a8ba8 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -31,9 +31,9 @@ from .exceptions import CStructException, ParserError if TYPE_CHECKING: - from .abstract import AbstractCStruct, AbstractCEnum + from .abstract import AbstractCStruct -__all__ = ['parse_struct', 'parse_struct_def', 'Tokens'] +__all__ = ['parse_struct', 'parse_struct_def', 'parse_enum_def', 'Tokens'] class Tokens(object): @@ -48,7 +48,7 @@ def __init__(self, text: str) -> None: _, name, value = line.strip().split(maxsplit=2) DEFINES[name] = c_eval(value) except Exception: - raise ParserError("Parsing line {}".format(line)) + raise ParserError(f"Parsing line {line}") else: lines.append(line) text = " ".join(lines) @@ -135,9 +135,8 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt try: ref = STRUCTS[tail] except KeyError: - raise ParserError("Unknow {} {}".format(c_type, tail)) + raise ParserError(f"Unknown '{c_type}' '{tail}'") elif c_type.startswith('enum'): - # from .abstract import AbstractCEnum from .cenum import CEnum c_type, tail = c_type.split(' ', 1) @@ -175,8 +174,10 @@ def parse_struct_def( if not tokens: return None kind = tokens.pop() + if kind == 'enum': + return parse_enum_def(__def__, **kargs) if kind not in ['struct', 'union']: - raise ParserError("struct or union expected - {}".format(kind)) + raise ParserError("struct, union, or enum expected - {kind}") __is_union__ = kind == 'union' vtype = tokens.pop() if tokens.get() == '{': # Named nested struct @@ -185,7 +186,7 @@ def parse_struct_def( elif vtype == '{': # Unnamed nested struct return parse_struct(tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__) else: - raise ParserError("{} definition expected".format(vtype)) + raise ParserError(f"{vtype} definition expected") def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, Any]]: @@ -242,7 +243,7 @@ def parse_enum(__enum__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, break if len(exp_elems) == 0: - raise ParserError(f"enum is missing value expression") + raise ParserError("enum is missing value expression") int_expr = " ".join(exp_elems) try: @@ -259,7 +260,12 @@ def parse_enum(__enum__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, if next_token == "}": break - result = {'__constants__': constants} + result = { + '__constants__': constants, + '__is_struct__': False, + '__is_union__': False, + '__is_enum__': True, + } return result @@ -325,7 +331,9 @@ def parse_struct( '__fields__': list(fields_types.keys()), '__fields_types__': fields_types, '__size__': size, + '__is_struct__': not __is_union__, '__is_union__': __is_union__, + '__is_enum__': False, '__byte_order__': __byte_order__, '__alignment__': max_alignment, } From 87ce6d3e4f7273d27230f59f591846e7ed5261f1 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 18:10:14 +0000 Subject: [PATCH 31/78] Add some enum tests --- tests/test_cenum.py | 83 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/tests/test_cenum.py b/tests/test_cenum.py index c9184b9..7e84c7a 100644 --- a/tests/test_cenum.py +++ b/tests/test_cenum.py @@ -1,14 +1,42 @@ +import pytest +import cstruct from cstruct import CEnum +from enum import Enum class Dummy(CEnum): __enum__ = """ - A, - B, - C = 2, - D = 5 + 7, - E = 2 - """ + #define SOME_DEFINE 7 + + A, + B, + C = 2, + D = 5 + SOME_DEFINE, + E = 2 + """ + + +class HtmlFont(CEnum): + __size__ = 2 + __def__ = """ + #define NONE 0 + + enum htmlfont { + HTMLFONT_NONE = NONE, + HTMLFONT_BOLD, + HTMLFONT_ITALIC, + }; + """ + + +class StructWithEnum(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct StructWithEnum { + enum HtmlFont font; + unsigned int font_size; + } + """ def test_dummy(): @@ -17,3 +45,46 @@ def test_dummy(): assert Dummy.C == 2 assert Dummy.D == 12 assert Dummy.E == 2 + + +def test_missing_attribute(): + with pytest.raises(AttributeError): + assert Dummy.F + + +def test_set_attribute(): + with pytest.raises(AttributeError): + Dummy.A = 100 + + +def test_html_font(): + assert HtmlFont.HTMLFONT_NONE == 0 + assert HtmlFont.HTMLFONT_BOLD == 1 + assert HtmlFont.HTMLFONT_ITALIC == 2 + + +def test_parse_enum(): + reply_stat = cstruct.parse("enum reply_stat { MSG_ACCEPTED=0, MSG_DENIED=1 }") + assert isinstance(reply_stat.MSG_ACCEPTED, Enum) + assert isinstance(reply_stat.MSG_DENIED, Enum) + assert reply_stat.MSG_ACCEPTED == 0 + assert reply_stat.MSG_DENIED == 1 + + +def test_struct_with_enum(): + s = StructWithEnum() + s.font = HtmlFont.HTMLFONT_BOLD + s.font_size = 11 + assert s.font == HtmlFont.HTMLFONT_BOLD + assert s.font_size == 11 + + s.font = HtmlFont.HTMLFONT_NONE + s.font_size = 20 + assert s.font == HtmlFont.HTMLFONT_NONE + assert s.font_size == 20 + packed = s.pack() + + s1 = StructWithEnum() + s1.unpack(packed) + assert s1.font == HtmlFont.HTMLFONT_NONE + assert s1.font_size == 20 From d9e7c68aeebd3e9296c793e92dc8576953af8492 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 1 Nov 2022 18:14:01 +0000 Subject: [PATCH 32/78] Update README.md --- README.md | 268 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) diff --git a/README.md b/README.md index 44e9095..2a9f272 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,274 @@ serializing the values into an array of bytes. [Api Documentation](https://python-cstruct.readthedocs.io/en/latest/) +Install +------- + +``` +pip install cstruct +``` + +Features +-------- + +### Structs + +Struct definition subclassing `cstruct.MemCStruct` + +```python +class Position(cstruct.MemCStruct): + __def__ = """ + struct { + unsigned char head; + unsigned char sector; + unsigned char cyl; + } + """ + +pos = Position(head=10, sector=20, cyl=30) +print(f"head: {pos.head} sector: {pos.sector} cyl: {pos.cyl}") +``` + +Struct definition using `cstruct.parse` + +```python +Partition = cstruct.parse(""" + struct { + #define ACTIVE_FLAG 0x80 + + unsigned char status; /* 0x80 - active */ + struct Position start; + unsigned char partition_type; + struct Position end; + unsigned int start_sect; /* starting sector counting from 0 */ + unsigned int sectors; /* nr of sectors in partition */ + } +""") + +part = Partition() +part.status = cstruct.getdef('ACTIVE_FLAG') +``` + +### Unions + +Union definition subclassing `cstruct.MemCStruct` + +```python +class Data(cstruct.MemCStruct): + __def__ = """ + union { + int integer; + float real; + } + """ + +data = Data() +data.integer = 2 +data.real = 3 +assert data.integer != 2 +``` + +### Enums + +Enum definition subclassing `cstruct.CEnum` + +```python +class HtmlFont(cstruct.CEnum): + __size__ = 2 + __def__ = """ + #define NONE 0 + + enum htmlfont { + HTMLFONT_NONE = NONE, + HTMLFONT_BOLD, + HTMLFONT_ITALIC + } + """ + +assert HtmlFont.HTMLFONT_NONE == 0 +assert HtmlFont.HTMLFONT_BOLD == 1 +assert HtmlFont.HTMLFONT_ITALIC == 2 +``` + +Different supported `__def__` styles: + +```c +enum Type_A a; // externally defined using CEnum +enum Type_B {A, B, C} b; +enum {A, B, C} c; +``` + +```python +class Type_A(cstruct.CEnum): + __size__ = 2 + __enum__ = """ + #define SOME_DEFINE 7 + + A, + B, + C = 5, + D, + E = 7 + SOME_DEFINE + """ + +# this is a nice gimmick that works, but wasn't really planned to be supported +class Type_C(cstruct.CEnum): + A = 0, + B = 1, + C = 2, + D = 3 +``` + +### Nested structures (named/anonymous) + +```python +class Packet(cstruct.MemCStruct): + __def__ = """ + struct Packet { + uint8_t packetLength; + union { + struct { + uint16_t field1; + uint16_t field2; + uint16_t field3; + } format1; + struct { + double value1; + double value2; + } format2; + }; + }; + """ +``` + +### Byte Order, Size, and Padding + +Suported byte orders: + +* `cstruct.LITTLE_ENDIAN` - Little endian byte order, standard size, no padding +* `cstruct.BIG_ENDIAN` - Big endian byte order, standard size, no padding +* `cstruct.NATIVE_ORDER` - Native byte order, native size, padding + +```python +class Native(cstruct.MemCStruct): + __byte_order__ = cstruct.NATIVE_ORDER + __def__ = """ + struct { + long p; + char c; + long x; + } + """ +``` + +### Flexible Array Member + +```python +class Pkg(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct { + uint16_t cmd; + uint16_t length; + uint8_t data[]; + } + """ + +pkg = Pkg() +pkg.length = 4 +pkg.data = [10, 20, 30, 40] +``` + +### Pack and Unpack + +```python +class StructWithEnum(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct StructWithEnum { + enum HtmlFont font; + unsigned int font_size; + } + """ + +# Pack +s.font = HtmlFont.HTMLFONT_NONE +s.font_size = 20 +assert s.font == HtmlFont.HTMLFONT_NONE +assert s.font_size == 20 +packed = s.pack() + +# Unpack +s1 = StructWithEnum() +s1.unpack(packed) +assert s1.font == HtmlFont.HTMLFONT_NONE +assert s1.font_size == 20 +``` + +### Define, Sizeof, and Eval + +Definitions in Struct declaration: + +```python +class Packet(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + #define MaxPacket 20 + + struct Packet { + uint8_t bytes[MaxPacket]; + } + """ +``` + +Parse C definitions: + +```python +cstruct.parse(""" + #define A1 10 + #define A2 10 + A1 + #define A3 30 +""") +assert cstruct.getdef("A1") == 10 +assert cstruct.getdef('A2') == 20 +``` + +Get structure size: + +```python +cstruct.sizeof(Partition) +``` + +Evaluate C expression: + +```python +cstruct.c_eval("A1 / 10") +cstruct.c_eval("((A10 < 6) || (A10>10))") +``` + +C expressions are automatically evaluated during structure definitions: + +```python +class MBR(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __struct__ = """ + #define MBR_SIZE 512 + #define MBR_DISK_SIGNATURE_SIZE 4 + #define MBR_USUALY_NULLS_SIZE 2 + #define MBR_SIGNATURE_SIZE 2 + #define MBR_BOOT_SIGNATURE 0xaa55 + #define MBR_PARTITIONS_NUM 4 + #define MBR_PARTITIONS_SIZE (sizeof(Partition) * MBR_PARTITIONS_NUM) + #define MBR_UNUSED_SIZE (MBR_SIZE - MBR_DISK_SIGNATURE_SIZE - MBR_USUALY_NULLS_SIZE - MBR_PARTITIONS_SIZE - MBR_SIGNATURE_SIZE) + + char unused[MBR_UNUSED_SIZE]; + unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; + unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; + struct Partition partitions[MBR_PARTITIONS_NUM]; + uint16 signature; + """ +``` + Example ------- From 275f57d8d1de9e9d5789a7aa2cb556c94551aa8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 2 Nov 2022 08:22:01 +0100 Subject: [PATCH 33/78] exclude tests from being installed Do not install `tests` as a top-level package -- such a package name is bound to collide with other packages, and for this reason no package should be installing files there. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33bf4e5..bfeaed7 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def readme(): author_email='andrea.bonomi@gmail.com', url='http://github.com/andreax79/python-cstruct', license='MIT', - packages=find_packages(exclude=['ez_setup', 'examples']), + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=True, install_requires=[ From d69ee4a6fd2c27691c9c0ad3362dc9d6353e89e1 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 2 Nov 2022 11:17:47 +0000 Subject: [PATCH 34/78] Update README.md --- README.md | 66 +++++++++++++++++++++++++++++++-------------------- docs/index.md | 13 +--------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 2a9f272..28934a5 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,13 @@ C-style structs for Python Convert C struct/union definitions into Python classes with methods for serializing/deserializing. + The usage is very simple: create a class subclassing cstruct.MemCStruct -and add a C struct/union definition as a string in the __struct__ field. +and add a C struct/union definition as a string in the `__def__` field. + The C struct/union definition is parsed at runtime and the struct format string -is generated. The class offers the method "unpack" for deserializing -an array of bytes into a Python object and the method "pack" for +is generated. The class offers the method `unpack` for deserializing +an array of bytes into a Python object and the method `pack` for serializing the values into an array of bytes. [Api Documentation](https://python-cstruct.readthedocs.io/en/latest/) @@ -272,7 +274,7 @@ C expressions are automatically evaluated during structure definitions: ```python class MBR(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ + __def__ = """ #define MBR_SIZE 512 #define MBR_DISK_SIGNATURE_SIZE 4 #define MBR_USUALY_NULLS_SIZE 2 @@ -282,11 +284,13 @@ class MBR(cstruct.MemCStruct): #define MBR_PARTITIONS_SIZE (sizeof(Partition) * MBR_PARTITIONS_NUM) #define MBR_UNUSED_SIZE (MBR_SIZE - MBR_DISK_SIGNATURE_SIZE - MBR_USUALY_NULLS_SIZE - MBR_PARTITIONS_SIZE - MBR_SIGNATURE_SIZE) - char unused[MBR_UNUSED_SIZE]; - unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; - unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; - struct Partition partitions[MBR_PARTITIONS_NUM]; - uint16 signature; + struct { + char unused[MBR_UNUSED_SIZE]; + unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; + unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; + struct Partition partitions[MBR_PARTITIONS_NUM]; + uint16 signature; + } """ ``` @@ -301,24 +305,30 @@ import cstruct class Position(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - unsigned char head; - unsigned char sector; - unsigned char cyl; + __def__ = """ + struct { + unsigned char head; + unsigned char sector; + unsigned char cyl; + } """ class Partition(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ + __def__ = """ #define ACTIVE_FLAG 0x80 - unsigned char status; /* 0x80 - active */ - struct Position start; - unsigned char partition_type; - struct Position end; - unsigned int start_sect; /* starting sector counting from 0 */ - unsigned int sectors; /* nr of sectors in partition */ + typedef struct Position Position; + + struct { + unsigned char status; /* 0x80 - active */ + Position start; + unsigned char partition_type; + Position end; + unsigned int start_sect; /* starting sector counting from 0 */ + unsigned int sectors; /* nr of sectors in partition */ + } """ def print_info(self): @@ -332,7 +342,7 @@ class Partition(cstruct.MemCStruct): class MBR(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ + __def__ = """ #define MBR_SIZE 512 #define MBR_DISK_SIGNATURE_SIZE 4 #define MBR_USUALY_NULLS_SIZE 2 @@ -342,11 +352,15 @@ class MBR(cstruct.MemCStruct): #define MBR_PARTITIONS_SIZE (sizeof(Partition) * MBR_PARTITIONS_NUM) #define MBR_UNUSED_SIZE (MBR_SIZE - MBR_DISK_SIGNATURE_SIZE - MBR_USUALY_NULLS_SIZE - MBR_PARTITIONS_SIZE - MBR_SIGNATURE_SIZE) - char unused[MBR_UNUSED_SIZE]; - unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; - unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; - struct Partition partitions[MBR_PARTITIONS_NUM]; - uint16 signature; + typedef struct Partition Partition; + + struct { + char unused[MBR_UNUSED_SIZE]; + unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; + unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; + Partition partitions[MBR_PARTITIONS_NUM]; + uint16 signature; + } """ @property diff --git a/docs/index.md b/docs/index.md index 9850dfb..563ed56 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1 @@ -# Python-CStruct - -Convert C struct/union definitions into Python classes with methods for -serializing/deserializing. - -The usage is very simple: create a class subclassing cstruct.MemCStruct -and add a C struct/union definition as a string in the `__struct__` field. - -The C struct/union definition is parsed at runtime and the struct format string -is generated. The class offers the method `unpack` for deserializing -an array of bytes into a Python object and the method `pack` for -serializing the values into an array of bytes. +{!README.md!} From de1168763326e034295e7b9f2339041bb2bf4c03 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 2 Nov 2022 11:18:47 +0000 Subject: [PATCH 35/78] Add typedef parsing --- cstruct/c_parser.py | 21 ++++++++++++- tests/test_typdef.py | 73 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/test_typdef.py diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index f7a8ba8..60b3e99 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -160,6 +160,22 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt return FieldType(kind, c_type, ref, vlen, flexible_array, byte_order, offset) +def parse_typedef(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Optional[str]) -> None: + field_type = parse_type(tokens, __cls__, byte_order, 0) + vname = tokens.pop() + if field_type.ref is None: + TYPEDEFS[vname] = field_type.c_type + elif field_type.ref.__is_enum__: + TYPEDEFS[vname] = f"enum {field_type.ref.__name__}" + elif field_type.ref.__is_union__: + TYPEDEFS[vname] = f"union {field_type.ref.__name__}" + else: + TYPEDEFS[vname] = f"struct {field_type.ref.__name__}" + t = tokens.pop() + if t != ';': + raise ParserError(f"; expected but {t} found") + + def parse_struct_def( __def__: Union[str, Tokens], __cls__: Type['AbstractCStruct'], @@ -174,10 +190,13 @@ def parse_struct_def( if not tokens: return None kind = tokens.pop() + if kind == 'typedef': + parse_typedef(tokens, __cls__, __byte_order__) + return parse_struct_def(tokens, __cls__, __byte_order__, **kargs) if kind == 'enum': return parse_enum_def(__def__, **kargs) if kind not in ['struct', 'union']: - raise ParserError("struct, union, or enum expected - {kind}") + raise ParserError(f"struct, union, or enum expected - {kind}") __is_union__ = kind == 'union' vtype = tokens.pop() if tokens.get() == '{': # Named nested struct diff --git a/tests/test_typdef.py b/tests/test_typdef.py new file mode 100644 index 0000000..d02eaa3 --- /dev/null +++ b/tests/test_typdef.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# ***************************************************************************** +# +# Copyright (c) 2013-2019 Andrea Bonomi +# +# Published under the terms of the MIT license. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# ***************************************************************************** + +from cstruct import MemCStruct, NATIVE_ORDER +from cstruct.base import TYPEDEFS + +class ExitStatus(MemCStruct): + __def__ = """ + struct ExitStatus { + short e_termination; /* Process termination status. */ + short e_exit; /* Process exit status. */ + } + """ + +class Utmp(MemCStruct): + __byte_order__ = NATIVE_ORDER + __def__ = """ + #define UT_NAMESIZE 32 + #define UT_LINESIZE 32 + #define UT_HOSTSIZE 256 + + typedef int pid_t; + typedef long time_t; + typedef unsigned long int ulong; + typedef struct ExitStatus ExitStatus; + + struct { + short ut_type; /* Type of record */ + pid_t ut_pid; /* PID of login process */ + char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */ + char ut_id[4]; /* Terminal name suffix, or inittab(5) ID */ + char ut_user[UT_NAMESIZE]; /* Username */ + char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or kernel version for run-level messages */ + ExitStatus ut_exit; /* Exit status of a process marked as DEAD_PROCESS; not used by Linux init (1 */ + int32_t ut_session; /* Session ID (getsid(2)), used for windowing */ + struct { + int32_t tv_sec; /* Seconds */ + int32_t tv_usec; /* Microseconds */ + } ut_tv; /* Time entry was made */ + int32_t ut_addr_v6[4]; /* Internet address of remote host; IPv4 address uses just ut_addr_v6[0] */ + char __unused[20]; /* Reserved for future use */ + } + """ + +def test_typedef(): + assert TYPEDEFS['pid_t'] == 'int' + assert TYPEDEFS['time_t'] == 'long' + assert TYPEDEFS['ulong'] == 'unsigned long' + assert TYPEDEFS['ExitStatus'] == 'struct ExitStatus' From 5df786d4b857e68ba643f8df70177f92c7ffccdf Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 2 Nov 2022 11:19:09 +0000 Subject: [PATCH 36/78] Update examples --- examples/fdisk.py | 45 ++++++++++++++++++------------ examples/fdisk.sh | 3 ++ examples/flexible_array.py | 10 ++++--- examples/flexible_array.sh | 3 ++ examples/who.py | 56 ++++++++++++++++++++++---------------- examples/who.sh | 3 ++ 6 files changed, 74 insertions(+), 46 deletions(-) create mode 100755 examples/fdisk.sh create mode 100755 examples/flexible_array.sh create mode 100755 examples/who.sh diff --git a/examples/fdisk.py b/examples/fdisk.py index 9aadf46..fdb352c 100644 --- a/examples/fdisk.py +++ b/examples/fdisk.py @@ -49,24 +49,29 @@ class Position(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - unsigned char head; - unsigned char sector; - unsigned char cyl; + __def__ = """ + struct { + unsigned char head; + unsigned char sector; + unsigned char cyl; + } """ class Partition(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ + __def__ = """ #define ACTIVE_FLAG 0x80 - - unsigned char status; /* 0x80 - active */ - struct Position start; - unsigned char partition_type; - struct Position end; - unsigned int start_sect; /* starting sector counting from 0 */ - unsigned int sectors; /* nr of sectors in partition */ + typedef struct Position Position; + + struct { + unsigned char status; /* 0x80 - active */ + Position start; + unsigned char partition_type; + Position end; + unsigned int start_sect; /* starting sector counting from 0 */ + unsigned int sectors; /* nr of sectors in partition */ + } """ @property @@ -96,7 +101,7 @@ def __str__(self): class MBR(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ + __def__ = """ #define MBR_SIZE 512 #define MBR_DISK_SIGNATURE_SIZE 4 #define MBR_USUALY_NULLS_SIZE 2 @@ -106,11 +111,15 @@ class MBR(cstruct.MemCStruct): #define MBR_PARTITIONS_SIZE (sizeof(Partition) * MBR_PARTITIONS_NUM) #define MBR_UNUSED_SIZE (MBR_SIZE - MBR_DISK_SIGNATURE_SIZE - MBR_USUALY_NULLS_SIZE - MBR_PARTITIONS_SIZE - MBR_SIGNATURE_SIZE) - char unused[MBR_UNUSED_SIZE]; - unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; - unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; - struct Partition partitions[MBR_PARTITIONS_NUM]; - uint16 signature; + typedef struct Partition Partition; + + struct { + char unused[MBR_UNUSED_SIZE]; + unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; + unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; + Partition partitions[MBR_PARTITIONS_NUM]; + uint16 signature; + } """ @property diff --git a/examples/fdisk.sh b/examples/fdisk.sh new file mode 100755 index 0000000..d81478d --- /dev/null +++ b/examples/fdisk.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cd "$(dirname "$0")/.." || exit +python -m examples.fdisk $* diff --git a/examples/flexible_array.py b/examples/flexible_array.py index 7a46764..4f8c28a 100644 --- a/examples/flexible_array.py +++ b/examples/flexible_array.py @@ -6,10 +6,12 @@ class FlexArray(MemCStruct): - __struct__ = """ - int length; - uint32 checksum; - long data[]; + __def__ = """ + struct { + int length; + uint32 checksum; + long data[]; + } """ def set_length(self, length): diff --git a/examples/flexible_array.sh b/examples/flexible_array.sh new file mode 100755 index 0000000..73d2cc2 --- /dev/null +++ b/examples/flexible_array.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cd "$(dirname "$0")/.." || exit +python -m examples.flexible_array $* diff --git a/examples/who.py b/examples/who.py index c18d2b4..0569cd1 100644 --- a/examples/who.py +++ b/examples/who.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from cstruct import parse, getdef, typedef, MemCStruct, NATIVE_ORDER +from cstruct import parse, getdef, MemCStruct, NATIVE_ORDER from pathlib import Path import argparse import sys @@ -30,24 +30,28 @@ #define UT_LINESIZE 32 #define UT_NAMESIZE 32 #define UT_HOSTSIZE 256 + +typedef int pid_t; +typedef long time_t; """ ) -typedef("int", "pid_t") -typedef("long", "time_t") - class ExitStatus(MemCStruct): - __struct__ = """ - short e_termination; /* Process termination status. */ - short e_exit; /* Process exit status. */ + __def__ = """ + struct ExitStatus { + short e_termination; /* Process termination status. */ + short e_exit; /* Process exit status. */ + } """ class Timeval(MemCStruct): - __struct__ = """ - int32_t tv_sec; /* Seconds. */ - int32_t tv_usec; /* Microseconds. */ + __def__ = """ + struct { + int32_t tv_sec; /* Seconds. */ + int32_t tv_usec; /* Microseconds. */ + } """ @@ -57,21 +61,25 @@ def str_from_c(string): class Utmp(MemCStruct): __byte_order__ = NATIVE_ORDER - __struct__ = """ - short ut_type; /* Type of record */ - pid_t ut_pid; /* PID of login process */ - char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */ - char ut_id[4]; /* Terminal name suffix, or inittab(5) ID */ - char ut_user[UT_NAMESIZE]; /* Username */ - char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or kernel version for run-level messages */ - struct ExitStatus ut_exit; /* Exit status of a process marked as DEAD_PROCESS; not used by Linux init (1 */ - int32_t ut_session; /* Session ID (getsid(2)), used for windowing */ + __def__ = """ + typedef struct ExitStatus ExitStatus; + struct { - int32_t tv_sec; /* Seconds */ - int32_t tv_usec; /* Microseconds */ - } ut_tv; /* Time entry was made */ - int32_t ut_addr_v6[4]; /* Internet address of remote host; IPv4 address uses just ut_addr_v6[0] */ - char __unused[20]; /* Reserved for future use */ + short ut_type; /* Type of record */ + pid_t ut_pid; /* PID of login process */ + char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */ + char ut_id[4]; /* Terminal name suffix, or inittab(5) ID */ + char ut_user[UT_NAMESIZE]; /* Username */ + char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or kernel version for run-level messages */ + ExitStatus ut_exit; /* Exit status of a process marked as DEAD_PROCESS; not used by Linux init (1 */ + int32_t ut_session; /* Session ID (getsid(2)), used for windowing */ + struct { + int32_t tv_sec; /* Seconds */ + int32_t tv_usec; /* Microseconds */ + } ut_tv; /* Time entry was made */ + int32_t ut_addr_v6[4]; /* Internet address of remote host; IPv4 address uses just ut_addr_v6[0] */ + char __unused[20]; /* Reserved for future use */ + } """ @property diff --git a/examples/who.sh b/examples/who.sh new file mode 100755 index 0000000..467de69 --- /dev/null +++ b/examples/who.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cd "$(dirname "$0")/.." || exit +python -m examples.who $* From f3fedb5af150c280ad8568a8d52be2d18cf1cd7b Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 2 Nov 2022 22:12:22 +0000 Subject: [PATCH 37/78] Add check for reserved fields names --- cstruct/c_parser.py | 10 ++++++---- tests/test_cstruct.py | 5 ++++- tests/test_memcstruct.py | 5 ++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 60b3e99..e578acc 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -316,15 +316,17 @@ def parse_struct( field_type = parse_type(tokens, __cls__, __byte_order__, offset) vname = tokens.pop() if vname in fields_types: - raise ParserError("Duplicate member '{}'".format(vname)) + raise ParserError(f"Duplicate member '{vname}'") + if vname in dir(__cls__): + raise ParserError(f"Invalid reserved member name '{vname}'") # anonymous nested union if vname == ';' and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): # add the anonymous struct fields to the parent for nested_field_name, nested_field_type in field_type.ref.__fields_types__.items(): if nested_field_name in fields_types: - raise ParserError("Duplicate member '{}'".format(nested_field_name)) + raise ParserError(f"Duplicate member '{nested_field_name}'") fields_types[nested_field_name] = nested_field_type - vname = "__anonymous{}".format(anonymous) + vname = f"__anonymous{anonymous}" anonymous += 1 tokens.push(';') fields_types[vname] = field_type @@ -336,7 +338,7 @@ def parse_struct( offset = field_type.offset + field_type.vsize t = tokens.pop() if t != ';': - raise ParserError("; expected but {} found".format(t)) + raise ParserError(f"; expected but {t} found") if __is_union__: # C union # Calculate the sizeof union as size of its largest element diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index e591273..66e6bca 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -262,4 +262,7 @@ def test_null_compare(): def test_invalid_inline(): with pytest.raises(ParserError): cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) - assert False + +def test_invalid_inline_reserved(): + with pytest.raises(ParserError): + cstruct.CStruct.parse('struct { int size; }') diff --git a/tests/test_memcstruct.py b/tests/test_memcstruct.py index 0ff95f8..047b9cb 100644 --- a/tests/test_memcstruct.py +++ b/tests/test_memcstruct.py @@ -256,4 +256,7 @@ def test_null_compare(): def test_invalid_inline(): with pytest.raises(ParserError): cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) - assert False + +def test_invalid_inline_reserved(): + with pytest.raises(ParserError): + cstruct.MemCStruct.parse('struct { int size; }') From 1dbf640dd960864deec090f4239890f5fa45f61e Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 2 Nov 2022 22:43:36 +0000 Subject: [PATCH 38/78] Add inspect method --- cstruct/abstract.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index a7d9903..d06e08b 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -29,6 +29,7 @@ from typing import Any, BinaryIO, List, Dict, Optional, Type, Tuple, Union import hashlib import sys +from io import StringIO from enum import IntEnum, EnumMeta, _EnumDict from unicodedata import name from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE @@ -226,6 +227,33 @@ def sizeof(cls) -> int: "Structure size in bytes (flexible array member size is omitted)" return cls.__size__ + def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = None) -> str: + """ + Return memory content in hexadecimal + + Args: + start_addr: start address + end_addr: end address + """ + buffer = StringIO() + if hasattr(self, '__mem__'): + mem = self.__mem__ + else: + mem = self.pack() + for i in range(start_addr or 0, end_addr or self.size, 16): + row = mem[i : i + 16] + buffer.write(f"{i:08x} ") + for j, c in enumerate(row): + separator = ' ' if j == 7 else '' + buffer.write(f" {c:02x}{separator}") + buffer.write(" |") + for c in row: + buffer.write(chr(c) if c >= 32 and c < 127 else '.') + buffer.write("|") + buffer.write("\n") + buffer.seek(0, 0) + return buffer.read() + def __eq__(self, other: Any) -> bool: return other is not None and isinstance(other, self.__class__) and self.__dict__ == other.__dict__ From 0ad6fbc15d5f34c96a6445c35590baa7298d904a Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 2 Nov 2022 22:44:57 +0000 Subject: [PATCH 39/78] Fix imports --- cstruct/abstract.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index d06e08b..9667ffe 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -28,14 +28,12 @@ from collections import OrderedDict from typing import Any, BinaryIO, List, Dict, Optional, Type, Tuple, Union import hashlib -import sys from io import StringIO from enum import IntEnum, EnumMeta, _EnumDict -from unicodedata import name from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE from .c_parser import parse_struct, parse_struct_def, parse_enum_def, parse_enum, Tokens from .field import calculate_padding, FieldType -from .exceptions import CStructException, CEnumException +from .exceptions import CStructException __all__ = ['CStructMeta', 'AbstractCStruct', "CEnumMeta", 'AbstractCEnum'] From e23fc1a660f44b4e894d48c9165263f9f2eba9c5 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 8 Nov 2022 07:49:24 +0000 Subject: [PATCH 40/78] documentation --- README.md | 215 +++++++++++++------------------- changelog.txt | 26 ++-- cstruct/c_parser.py | 25 +++- docs/CODE_OF_CONDUCT.md | 1 + docs/api/c_parser.md | 1 + docs/changelog.md | 1 + docs/examples/fdisk.md | 2 + docs/examples/flexible_array.md | 2 + docs/examples/who.md | 2 + mkdocs.yml | 8 +- 10 files changed, 135 insertions(+), 148 deletions(-) create mode 100644 docs/CODE_OF_CONDUCT.md create mode 100644 docs/api/c_parser.md create mode 100644 docs/changelog.md diff --git a/README.md b/README.md index 28934a5..cb6dee4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ C-style structs for Python Convert C struct/union definitions into Python classes with methods for serializing/deserializing. -The usage is very simple: create a class subclassing cstruct.MemCStruct +The usage is very simple: create a class subclassing +[`cstruct.MemCStruct`](https://python-cstruct.readthedocs.io/en/latest/api/mem_cstruct/) and add a C struct/union definition as a string in the `__def__` field. The C struct/union definition is parsed at runtime and the struct format string @@ -22,8 +23,6 @@ is generated. The class offers the method `unpack` for deserializing an array of bytes into a Python object and the method `pack` for serializing the values into an array of bytes. -[Api Documentation](https://python-cstruct.readthedocs.io/en/latest/) - Install ------- @@ -31,12 +30,20 @@ Install pip install cstruct ``` +Examples +-------- + +* [Read the DOS-type (MBR) partition table](https://python-cstruct.readthedocs.io/en/latest/examples/fdisk/) +* [Print information about logged uses](https://python-cstruct.readthedocs.io/en/latest/examples/who/) +* [Flexible Array Member (FAM)](https://python-cstruct.readthedocs.io/en/latest/examples/flexible_array/) + + Features -------- ### Structs -Struct definition subclassing `cstruct.MemCStruct` +Struct definition subclassing `cstruct.MemCStruct`. Methods can access stuct values as instance variables. ```python class Position(cstruct.MemCStruct): @@ -47,18 +54,21 @@ class Position(cstruct.MemCStruct): unsigned char cyl; } """ + @property + def lba(self): + return (self.cyl * 16 + self.head) * 63 + (self.sector - 1) -pos = Position(head=10, sector=20, cyl=30) -print(f"head: {pos.head} sector: {pos.sector} cyl: {pos.cyl}") +pos = Position(cyl=15, head=15, sector=63) +print(f"head: {pos.head} sector: {pos.sector} cyl: {pos.cyl} lba: {pos.lba}") ``` -Struct definition using `cstruct.parse` +Struct definition using `cstruct.parse`. ```python Partition = cstruct.parse(""" - struct { - #define ACTIVE_FLAG 0x80 + #define ACTIVE_FLAG 0x80 + struct Partition { unsigned char status; /* 0x80 - active */ struct Position start; unsigned char partition_type; @@ -74,7 +84,7 @@ part.status = cstruct.getdef('ACTIVE_FLAG') ### Unions -Union definition subclassing `cstruct.MemCStruct` +Union definition subclassing `cstruct.MemCStruct`. ```python class Data(cstruct.MemCStruct): @@ -93,7 +103,7 @@ assert data.integer != 2 ### Enums -Enum definition subclassing `cstruct.CEnum` +Enum definition subclassing `cstruct.CEnum`. ```python class HtmlFont(cstruct.CEnum): @@ -113,7 +123,7 @@ assert HtmlFont.HTMLFONT_BOLD == 1 assert HtmlFont.HTMLFONT_ITALIC == 2 ``` -Different supported `__def__` styles: +Different enum styles are supported in struct/union definitions. ```c enum Type_A a; // externally defined using CEnum @@ -121,28 +131,9 @@ enum Type_B {A, B, C} b; enum {A, B, C} c; ``` -```python -class Type_A(cstruct.CEnum): - __size__ = 2 - __enum__ = """ - #define SOME_DEFINE 7 - - A, - B, - C = 5, - D, - E = 7 + SOME_DEFINE - """ - -# this is a nice gimmick that works, but wasn't really planned to be supported -class Type_C(cstruct.CEnum): - A = 0, - B = 1, - C = 2, - D = 3 -``` +### Nested structs/unions -### Nested structures (named/anonymous) +Nested stucts and unions are supported, both named and anonymous. ```python class Packet(cstruct.MemCStruct): @@ -204,28 +195,33 @@ pkg.data = [10, 20, 30, 40] ### Pack and Unpack +A code example illustrating how to use +[`pack`](https://python-cstruct.readthedocs.io/en/latest/api/abstract/#cstruct.abstract.AbstractCStruct.pack) to pack a structure into binary form. + ```python -class StructWithEnum(cstruct.MemCStruct): +class Position(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN __def__ = """ - struct StructWithEnum { - enum HtmlFont font; - unsigned int font_size; + struct { + unsigned char head; + unsigned char sector; + unsigned char cyl; } """ -# Pack -s.font = HtmlFont.HTMLFONT_NONE -s.font_size = 20 -assert s.font == HtmlFont.HTMLFONT_NONE -assert s.font_size == 20 -packed = s.pack() - -# Unpack -s1 = StructWithEnum() -s1.unpack(packed) -assert s1.font == HtmlFont.HTMLFONT_NONE -assert s1.font_size == 20 +pos = Position(head=10, sector=20, cyl=3) +packed = pos.pack() +``` + +Binary representation can be converted into structure using +[`unpack`](https://python-cstruct.readthedocs.io/en/latest/api/abstract/#cstruct.abstract.AbstractCStruct.unpack). + +``` +pos1 = Position() +pos1.unpack(packed) +assert pos1.head == 10 +assert pos1.sector == 20 +assert pos1.cyl == 3 ``` ### Define, Sizeof, and Eval @@ -262,7 +258,7 @@ Get structure size: cstruct.sizeof(Partition) ``` -Evaluate C expression: +Evaluate C expression using [`c_eval`](https://python-cstruct.readthedocs.io/en/latest/api/c_expr/): ```python cstruct.c_eval("A1 / 10") @@ -294,90 +290,47 @@ class MBR(cstruct.MemCStruct): """ ``` -Example -------- +### Ispect memory -The following program reads the DOS partition information from a disk. +The [`inspect`](https://python-cstruct.readthedocs.io/en/latest/api/abstract/#cstruct.abstract.AbstractCStruct.inspect) methods displays memory contents in hexadecimal. ```python -#!/usr/bin/env python -import cstruct - -class Position(cstruct.MemCStruct): - __byte_order__ = cstruct.LITTLE_ENDIAN - __def__ = """ - struct { - unsigned char head; - unsigned char sector; - unsigned char cyl; - } - """ - - -class Partition(cstruct.MemCStruct): - __byte_order__ = cstruct.LITTLE_ENDIAN - __def__ = """ - #define ACTIVE_FLAG 0x80 - - typedef struct Position Position; - - struct { - unsigned char status; /* 0x80 - active */ - Position start; - unsigned char partition_type; - Position end; - unsigned int start_sect; /* starting sector counting from 0 */ - unsigned int sectors; /* nr of sectors in partition */ - } - """ - - def print_info(self): - print(f"bootable: {'Y' if self.status & cstruct.getdef('ACTIVE_FLAG') else 'N'}") - print(f"partition_type: {self.partition_type:02X}") - print(f"start: head: {self.start.head:X} sectory: {self.start.sector:X} cyl: {self.start.cyl:X}") - print(f"end: head: {self.end.head:X} sectory: {self.end.sector:X} cyl: {self.end.cyl:X}") - print(f"starting sector: {self.start_sect:08x}") - print(f"size MB: {self.sectors / 2 / 1024}") - - -class MBR(cstruct.MemCStruct): - __byte_order__ = cstruct.LITTLE_ENDIAN - __def__ = """ - #define MBR_SIZE 512 - #define MBR_DISK_SIGNATURE_SIZE 4 - #define MBR_USUALY_NULLS_SIZE 2 - #define MBR_SIGNATURE_SIZE 2 - #define MBR_BOOT_SIGNATURE 0xaa55 - #define MBR_PARTITIONS_NUM 4 - #define MBR_PARTITIONS_SIZE (sizeof(Partition) * MBR_PARTITIONS_NUM) - #define MBR_UNUSED_SIZE (MBR_SIZE - MBR_DISK_SIGNATURE_SIZE - MBR_USUALY_NULLS_SIZE - MBR_PARTITIONS_SIZE - MBR_SIGNATURE_SIZE) - - typedef struct Partition Partition; - - struct { - char unused[MBR_UNUSED_SIZE]; - unsigned char disk_signature[MBR_DISK_SIGNATURE_SIZE]; - unsigned char usualy_nulls[MBR_USUALY_NULLS_SIZE]; - Partition partitions[MBR_PARTITIONS_NUM]; - uint16 signature; - } - """ +print(mbr.inspect()) +``` - @property - def disk_signature_str(self): - return "".join(reversed([f"{x:02x}" for x in self.disk_signature])) - - def print_info(self): - print(f"disk signature: {self.disk_signature_str}") - for i, partition in enumerate(self.partitions): - print("") - print(f"partition: {i}") - partition.print_info() - -disk = "mbr" -with open(disk, "rb") as f: - mbr = MBR() - mbr.unpack(f) - mbr.print_info() +Output example: +``` +00000000 eb 48 90 00 00 00 00 00 00 00 00 00 00 00 00 00 |.H..............| +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 02 |................| +00000040 ff 00 00 80 61 cb 04 00 00 08 fa 80 ca 80 ea 53 |....a..........S| +00000050 7c 00 00 31 c0 8e d8 8e d0 bc 00 20 fb a0 40 7c ||..1....... ..@|| +00000060 3c ff 74 02 88 c2 52 be 79 7d e8 34 01 f6 c2 80 |<.t...R.y}.4....| +00000070 74 54 b4 41 bb aa 55 cd 13 5a 52 72 49 81 fb 55 |tT.A..U..ZRrI..U| +00000080 aa 75 43 a0 41 7c 84 c0 75 05 83 e1 01 74 37 66 |.uC.A|..u....t7f| +00000090 8b 4c 10 be 05 7c c6 44 ff 01 66 8b 1e 44 7c c7 |.L...|.D..f..D|.| +000000a0 04 10 00 c7 44 02 01 00 66 89 5c 08 c7 44 06 00 |....D...f.\..D..| +000000b0 70 66 31 c0 89 44 04 66 89 44 0c b4 42 cd 13 72 |pf1..D.f.D..B..r| +000000c0 05 bb 00 70 eb 7d b4 08 cd 13 73 0a f6 c2 80 0f |...p.}....s.....| +000000d0 84 f0 00 e9 8d 00 be 05 7c c6 44 ff 00 66 31 c0 |........|.D..f1.| +000000e0 88 f0 40 66 89 44 04 31 d2 88 ca c1 e2 02 88 e8 |..@f.D.1........| +000000f0 88 f4 40 89 44 08 31 c0 88 d0 c0 e8 02 66 89 04 |..@.D.1......f..| +00000100 66 a1 44 7c 66 31 d2 66 f7 34 88 54 0a 66 31 d2 |f.D|f1.f.4.T.f1.| +00000110 66 f7 74 04 88 54 0b 89 44 0c 3b 44 08 7d 3c 8a |f.t..T..D.;D.}<.| +00000120 54 0d c0 e2 06 8a 4c 0a fe c1 08 d1 8a 6c 0c 5a |T.....L......l.Z| +00000130 8a 74 0b bb 00 70 8e c3 31 db b8 01 02 cd 13 72 |.t...p..1......r| +00000140 2a 8c c3 8e 06 48 7c 60 1e b9 00 01 8e db 31 f6 |*....H|`......1.| +00000150 31 ff fc f3 a5 1f 61 ff 26 42 7c be 7f 7d e8 40 |1.....a.&B|..}.@| +00000160 00 eb 0e be 84 7d e8 38 00 eb 06 be 8e 7d e8 30 |.....}.8.....}.0| +00000170 00 be 93 7d e8 2a 00 eb fe 47 52 55 42 20 00 47 |...}.*...GRUB .G| +00000180 65 6f 6d 00 48 61 72 64 20 44 69 73 6b 00 52 65 |eom.Hard Disk.Re| +00000190 61 64 00 20 45 72 72 6f 72 00 bb 01 00 b4 0e cd |ad. Error.......| +000001a0 10 ac 3c 00 75 f4 c3 00 00 00 00 00 00 00 00 00 |..<.u...........| +000001b0 00 00 00 00 00 00 00 00 40 e2 01 00 00 00 80 00 |........@.......| +000001c0 02 00 83 fe 3f 86 01 00 00 00 c6 17 21 00 00 00 |....?.......!...| +000001d0 01 87 8e fe ff ff c7 17 21 00 4d d3 de 00 00 00 |........!.M.....| +000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| ``` diff --git a/changelog.txt b/changelog.txt index 65b3659..5a46494 100644 --- a/changelog.txt +++ b/changelog.txt @@ -46,7 +46,7 @@ - compatibiliy fix -### 1.6 +## 1.6 2017-12-12 @@ -54,7 +54,7 @@ - fixed size of 64-bit integers, they now have 64 bits, not 32 -### 1.7 +## 1.7 2018-03-14 @@ -62,7 +62,7 @@ - add support for // comments -### 1.8 +## 1.8 2018-10-30 @@ -75,7 +75,7 @@ - fix Python 2.5 support in main module - examples fix -### 1.9 +## 1.9 2019-07-09 @@ -88,7 +88,7 @@ - flexible array parsing - union initial support -### 2.0 +## 2.0 2020-04-11 @@ -96,7 +96,7 @@ - drop Python 2 support -### 2.1 +## 2.1 2020-10-09 @@ -106,7 +106,7 @@ - Python 3.9 support - Github workfows -### 2.2 +## 2.2 2022-08-23 @@ -120,7 +120,7 @@ - pytest - black code style -### 2.3 +## 2.3 2022-09-01 @@ -128,7 +128,7 @@ - Fix compare with None -### 3.0 +## 3.0 2022-09-05 @@ -136,7 +136,7 @@ - Flexible array support -### 3.1 +## 3.1 2022-09-13 @@ -144,7 +144,7 @@ - Make CStruct/MemCStruct Pickle Friendly -### 3.2 +## 3.2 2022-10-23 @@ -152,7 +152,7 @@ - Fix padding tests on 32bit architectures -### 3.3 +## 3.3 2022-10-24 @@ -164,7 +164,7 @@ - Fix padding tests on 32bit architectures -### 4.0 +## 4.0 2022-11-01 diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index e578acc..aaa453a 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -31,7 +31,7 @@ from .exceptions import CStructException, ParserError if TYPE_CHECKING: - from .abstract import AbstractCStruct + from .abstract import AbstractCStruct, AbstractCEnum __all__ = ['parse_struct', 'parse_struct_def', 'parse_enum_def', 'Tokens'] @@ -126,7 +126,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt if tokens.get() == '{': # Named nested struct tokens.push(tail) tokens.push(c_type) - ref = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) + ref: Union[Type[AbstractCEnum], Type[AbstractCStruct]] = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) elif tail == '{': # Unnamed nested struct tokens.push(tail) tokens.push(c_type) @@ -231,6 +231,15 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s def parse_enum(__enum__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, Any]]: + """ + Parser for C-like enum syntax. + + Args: + __enum__: definition of the enum in C syntax + + Returns: + dict: the parsed definition + """ constants: Dict[str, int] = OrderedDict() if isinstance(__enum__, Tokens): @@ -295,6 +304,18 @@ def parse_struct( __byte_order__: Optional[str] = None, **kargs: Any, ) -> Dict[str, Any]: + """ + Parser for C-like struct syntax. + + Args: + __struct__: definition of the struct/union in C syntax + __cls__: base class (MemCStruct or CStruct) + __is_union__: True for union, False for struct + __byte_order__: byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER + + Returns: + dict: the parsed definition + """ # naive C struct parsing __is_union__ = bool(__is_union__) fields_types: Dict[str, FieldType] = OrderedDict() diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..97b4f23 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +{!CODE_OF_CONDUCT.md!} diff --git a/docs/api/c_parser.md b/docs/api/c_parser.md new file mode 100644 index 0000000..1428994 --- /dev/null +++ b/docs/api/c_parser.md @@ -0,0 +1 @@ +::: cstruct.c_parser diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..85c3845 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ +{!changelog.txt!} diff --git a/docs/examples/fdisk.md b/docs/examples/fdisk.md index 1680788..5682f42 100644 --- a/docs/examples/fdisk.md +++ b/docs/examples/fdisk.md @@ -1,3 +1,5 @@ +The following program reads the DOS-type (MBR) partition table from a disk. + ``` {!examples/fdisk.py!} ``` diff --git a/docs/examples/flexible_array.md b/docs/examples/flexible_array.md index ea76b6e..041e1cd 100644 --- a/docs/examples/flexible_array.md +++ b/docs/examples/flexible_array.md @@ -1,3 +1,5 @@ +[Flexible Array Member (FAM)](https://en.wikipedia.org/wiki/Flexible_array_member) example. + ``` {!examples/flexible_array.py!} ``` diff --git a/docs/examples/who.md b/docs/examples/who.md index b4521ab..a2289e8 100644 --- a/docs/examples/who.md +++ b/docs/examples/who.md @@ -1,3 +1,5 @@ +The following program prints information about users who are currently logged in. + ``` {!examples/who.py!} ``` diff --git a/mkdocs.yml b/mkdocs.yml index 01fb098..45441d3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,7 +14,11 @@ plugins: - examples nav: - - CStruct Docs: index.md + - Introduction: index.md + - Changelog: changelog.md + - License: license.md + - Code of Conduct: CODE_OF_CONDUCT.md + - Source Code Repository: "https://github.com/andreax79/python-cstruct" - Examples: - "fdisk.py": examples/fdisk.md - "flexible_array.py": examples/flexible_array.md @@ -24,10 +28,10 @@ nav: - "cstruct.abstract": api/abstract.md - "cstruct.base": api/base.md - "cstruct.c_expr": api/c_expr.md + - "cstruct.c_parser": api/c_parser.md - "cstruct.cstruct": api/cstruct.md - "cstruct.field": api/field.md - "cstruct.mem_cstruct": api/mem_cstruct.md - - License: license.md markdown_extensions: - markdown_include.include: From 2d27fd3030fb88cc3cb44cfa30a8eb6f5a72dbaf Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Thu, 10 Nov 2022 07:19:19 +0000 Subject: [PATCH 41/78] Add support for multiple definition to cstruct.parse --- cstruct/__init__.py | 14 +++---- cstruct/abstract.py | 4 +- cstruct/c_parser.py | 91 ++++++++++++++++++++++++++++++++------------- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 7015ae3..94ffac8 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -134,28 +134,24 @@ def sizeof(type_: str) -> int: def parse( - __struct__: str, __cls__: Optional[Type[AbstractCStruct]] = None, __name__: Optional[str] = None, **kargs: Dict[str, Any] + __struct__: str, __cls__: Optional[Type[AbstractCStruct]] = None, **kargs: Dict[str, Any] ) -> Union[Type[AbstractCStruct], Type[AbstractCEnum], None]: """ Return a new class mapping a C struct/union/enum definition. If the string does not contains any definition, return None. + Args: __struct__ (str): definition of the struct (or union/enum) in C syntax __cls__ (type): super class - CStruct(default) or MemCStruct - __name__ (str): name of the new class. If empty, a name based on the __struct__ hash is generated __byte_order__ (str): byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER - __is_union__ (bool): True for union, False for struct (default) Returns: cls: __cls__ subclass """ if __cls__ is None: - __cls__ = CStruct - cls_def = parse_struct_def(__struct__, __cls__=__cls__, **kargs) + __cls__ = MemCStruct + cls_def = parse_struct_def(__struct__, __cls__=__cls__, process_muliple_definition=True, **kargs) if cls_def is None: return None - elif cls_def['__is_enum__']: - return AbstractCEnum.parse(cls_def, __name__, **kargs) - else: - return __cls__.parse(cls_def, __name__, **kargs) + return cls_def['__cls__'].parse(cls_def, **kargs) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 9667ffe..b1f4d6e 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -35,7 +35,7 @@ from .field import calculate_padding, FieldType from .exceptions import CStructException -__all__ = ['CStructMeta', 'AbstractCStruct', "CEnumMeta", 'AbstractCEnum'] +__all__ = ['CStructMeta', 'AbstractCStruct', 'CEnumMeta', 'AbstractCEnum'] class CStructMeta(ABCMeta): @@ -138,6 +138,7 @@ def parse( del cls_kargs['__struct__'] cls_kargs.update(__struct__) cls_kargs['__struct__'] = None + __name__ = cls_kargs.get('__name__') or __name__ if __name__ is None: # Anonymous struct __name__ = cls.__name__ + '_' + hashlib.sha1(str(__struct__).encode('utf-8')).hexdigest() cls_kargs['__anonymous__'] = True @@ -363,6 +364,7 @@ def parse( elif isinstance(__enum__, dict): cls_kargs.update(__enum__) + __name__ = cls_kargs.get('__name__') or __name__ if __name__ is None: __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest() cls_kargs["__anonymous__"] = True diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index aaa453a..e9b4591 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -180,6 +180,7 @@ def parse_struct_def( __def__: Union[str, Tokens], __cls__: Type['AbstractCStruct'], __byte_order__: Optional[str] = None, + process_muliple_definition: bool = False, **kargs: Any, # Type['AbstractCStruct'], ) -> Optional[Dict[str, Any]]: # naive C struct parsing @@ -187,25 +188,47 @@ def parse_struct_def( tokens = __def__ else: tokens = Tokens(__def__) - if not tokens: - return None - kind = tokens.pop() - if kind == 'typedef': - parse_typedef(tokens, __cls__, __byte_order__) - return parse_struct_def(tokens, __cls__, __byte_order__, **kargs) - if kind == 'enum': - return parse_enum_def(__def__, **kargs) - if kind not in ['struct', 'union']: - raise ParserError(f"struct, union, or enum expected - {kind}") - __is_union__ = kind == 'union' - vtype = tokens.pop() - if tokens.get() == '{': # Named nested struct - tokens.pop() - return parse_struct(tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__) - elif vtype == '{': # Unnamed nested struct - return parse_struct(tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__) - else: - raise ParserError(f"{vtype} definition expected") + result = None + while tokens and (process_muliple_definition or not result): + kind = tokens.pop() + if kind == ';': + pass + + elif kind == 'typedef': + if result: + result['__cls__'].parse(result, **kargs) + parse_typedef(tokens, __cls__, __byte_order__) + + elif kind == 'enum': + if result: + result['__cls__'].parse(result, **kargs) + name = tokens.pop() + if tokens.get() == '{': # named enum + tokens.pop() # pop "{" + result = parse_enum(tokens, __name__=name) + elif name == '{': # unnamed enum + result = parse_enum(tokens) + else: + raise ParserError(f"{name} definition expected") + + elif kind in ['struct', 'union']: + if result: + result['__cls__'].parse(result, **kargs) + __is_union__ = kind == 'union' + name = tokens.pop() + if name == '{': # unnamed nested struct + result = parse_struct(tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__) + elif tokens.get() == '{': # Named nested struct + tokens.pop() # pop "{" + result = parse_struct( + tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__, __name__=name + ) + else: + raise ParserError(f"{name} definition expected") + + else: + raise ParserError(f"struct, union, or enum expected - {kind}") + return result def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, Any]]: @@ -220,26 +243,33 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s if kind not in ['enum']: raise ParserError(f"enum expected - {kind}") - vtype = tokens.pop() + name = tokens.pop() if tokens.get() == '{': # named enum - tokens.pop() - return parse_enum(tokens) - elif vtype == '{': + tokens.pop() # pop "{" + return parse_enum(tokens, __name__=name) + elif name == '{': # unnamed enum return parse_enum(tokens) else: - raise ParserError(f"{vtype} definition expected") + raise ParserError(f"{name} definition expected") -def parse_enum(__enum__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, Any]]: +def parse_enum( + __enum__: Union[str, Tokens], + __name__: Optional[str] = None, + **kargs: Any, +) -> Optional[Dict[str, Any]]: """ Parser for C-like enum syntax. Args: __enum__: definition of the enum in C syntax + __name__: enum name Returns: dict: the parsed definition """ + from .cenum import CEnum + constants: Dict[str, int] = OrderedDict() if isinstance(__enum__, Tokens): @@ -293,6 +323,8 @@ def parse_enum(__enum__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[str, '__is_struct__': False, '__is_union__': False, '__is_enum__': True, + '__name__': __name__, + '__cls__': CEnum, } return result @@ -302,6 +334,7 @@ def parse_struct( __cls__: Type['AbstractCStruct'], __is_union__: bool = False, __byte_order__: Optional[str] = None, + __name__: Optional[str] = None, **kargs: Any, ) -> Dict[str, Any]: """ @@ -312,11 +345,17 @@ def parse_struct( __cls__: base class (MemCStruct or CStruct) __is_union__: True for union, False for struct __byte_order__: byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER + __name__: struct/union name Returns: dict: the parsed definition """ # naive C struct parsing + from .abstract import AbstractCStruct + from .mem_cstruct import MemCStruct + + if __cls__ is None or __cls__ == AbstractCStruct: + __cls__ = MemCStruct __is_union__ = bool(__is_union__) fields_types: Dict[str, FieldType] = OrderedDict() flexible_array: bool = False @@ -378,5 +417,7 @@ def parse_struct( '__is_enum__': False, '__byte_order__': __byte_order__, '__alignment__': max_alignment, + '__name__': __name__, + '__cls__': __cls__, } return result From b2e37ed4776047cea5614c2bde8d41d990ef08fc Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Fri, 11 Nov 2022 07:03:41 +0000 Subject: [PATCH 42/78] cstruct.native_types and get_type added --- cstruct/__init__.py | 64 ++++++++--- cstruct/base.py | 28 +---- cstruct/field.py | 9 +- cstruct/native_types.py | 224 +++++++++++++++++++++++++++++++++++++++ docs/api/native_types.md | 1 + mkdocs.yml | 1 + tests/test_cstruct.py | 1 + tests/test_get_type.py | 82 ++++++++++++++ tests/test_memcstruct.py | 1 + tests/test_typdef.py | 3 + 10 files changed, 367 insertions(+), 47 deletions(-) create mode 100644 cstruct/native_types.py create mode 100644 docs/api/native_types.md create mode 100644 tests/test_get_type.py diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 94ffac8..8af6fcc 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -27,16 +27,15 @@ __version__ = '4.0' __date__ = '15 August 2013' -import struct from typing import Any, Dict, Optional, Type, Union from .base import ( LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER, STRUCTS, + ENUMS, DEFINES, TYPEDEFS, - C_TYPE_TO_FORMAT, CHAR_ZERO, ) from .abstract import CStructMeta, AbstractCStruct, AbstractCEnum @@ -44,6 +43,7 @@ from .c_parser import parse_struct_def from .mem_cstruct import MemCStruct from .cenum import CEnum +from .native_types import get_native_type __all__ = [ 'LITTLE_ENDIAN', @@ -57,6 +57,7 @@ 'undef', 'getdef', 'typedef', + 'get_type', 'sizeof', 'parse', ] @@ -104,33 +105,62 @@ def typedef(type_: str, alias: str) -> None: TYPEDEFS[alias] = type_ -def sizeof(type_: str) -> int: +def get_type(type_: str) -> Any: """ - Return the size of the type. + Get a data type (struct, union, enum) by name + + Examples: + >>> get_type("struct Position") + + >>> get_type("enum htmlfont") + Args: - type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ') + type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ'), enum or native type Returns: - size: size in bytes + class: data type class """ while type_ in TYPEDEFS: type_ = TYPEDEFS[type_] if isinstance(type_, CStructMeta): - return len(type_) + return type_ elif type_.startswith('struct ') or type_.startswith('union '): kind, type_ = type_.split(' ', 1) - t = STRUCTS.get(type_, None) - if t is None: - raise KeyError("Unknow %s \"%s\"" % (kind, type_)) - else: - return t.sizeof() + try: + return STRUCTS[type_] + except KeyError: + raise KeyError(f"Unknown {kind} `{type_}`") + elif type_.startswith('enum '): + kind, type_ = type_.split(' ', 1) + try: + return ENUMS[type_] + except KeyError: + raise KeyError(f"Unknown {kind} `{type_}`") else: - ttype = C_TYPE_TO_FORMAT.get(type_, None) - if ttype is None: - raise KeyError("Unknow type \"" + type_ + "\"") - else: - return struct.calcsize(ttype) + return get_native_type(type_) + + +def sizeof(type_: str) -> int: + """ + Return the size of the type. + + Examples: + >>> sizeof("struct Position") + 16 + >>> sizeof("int") + 4 + + Args: + type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ'), enum or native type + + Returns: + size: size in bytes + """ + while type_ in TYPEDEFS: + type_ = TYPEDEFS[type_] + data_type = get_type(type_) + return data_type.sizeof() def parse( diff --git a/cstruct/base.py b/cstruct/base.py index 96f2496..d764c85 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -35,8 +35,8 @@ 'STRUCTS', 'DEFINES', 'TYPEDEFS', - 'C_TYPE_TO_FORMAT', 'CHAR_ZERO', + 'DEFAULT_ENUM_SIZE', ] LITTLE_ENDIAN = '<' @@ -68,32 +68,8 @@ 'uint64_t': 'uint64', } -C_TYPE_TO_FORMAT: Dict[str, str] = { - 'char': 's', - 'signed char': 'b', - 'unsigned char': 'B', - 'short': 'h', - 'unsigned short': 'H', - 'int': 'i', - 'unsigned int': 'I', - 'long': 'l', - 'unsigned long': 'L', - 'long long': 'q', - 'unsigned long long': 'Q', - 'float': 'f', - 'double': 'd', - 'void *': 'P', - 'int8': 'b', - 'uint8': 'B', - 'int16': 'h', - 'uint16': 'H', - 'int32': 'i', - 'uint32': 'I', - 'int64': 'q', - 'uint64': 'Q', -} - ENUM_SIZE_TO_C_TYPE: Dict[int, str] = {1: 'int8', 2: 'int16', 4: 'int32', 8: 'int64'} CHAR_ZERO = bytes('\0', 'ascii') + DEFAULT_ENUM_SIZE = 4 diff --git a/cstruct/field.py b/cstruct/field.py index 08c5bc0..1533a2a 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -26,7 +26,8 @@ import struct from enum import Enum from typing import Optional, Any, List, Type, TYPE_CHECKING -from .base import NATIVE_ORDER, C_TYPE_TO_FORMAT, ENUM_SIZE_TO_C_TYPE +from .base import NATIVE_ORDER, ENUM_SIZE_TO_C_TYPE +from .native_types import get_native_type from .exceptions import ParserError if TYPE_CHECKING: @@ -188,12 +189,12 @@ def native_format(self) -> str: "Field format (struct library format)" if self.is_native: try: - return C_TYPE_TO_FORMAT[self.c_type] + return get_native_type(self.c_type).native_format except KeyError: - raise ParserError("Unknow type {}".format(self.c_type)) + raise ParserError(f"Unknow type `{self.c_type}`") elif self.is_enum: try: - return C_TYPE_TO_FORMAT[ENUM_SIZE_TO_C_TYPE[self.ref.size]] + return get_native_type(ENUM_SIZE_TO_C_TYPE[self.ref.size]).native_format except KeyError: raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}") else: diff --git a/cstruct/native_types.py b/cstruct/native_types.py new file mode 100644 index 0000000..517930d --- /dev/null +++ b/cstruct/native_types.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# Copyright (c) 2013-2019 Andrea Bonomi +# +# Published under the terms of the MIT license. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# + +import struct +from abc import ABCMeta +from typing import Any, Dict, Type, Tuple + +__all__ = [ + "get_native_type", + "AbstractNativeType", + "Char", + "SignedChar", + "UnsignedChar", + "Short", + "UnsignedShort", + "Int", + "UnsignedInt", + "Long", + "UnsignedLong", + "LongLong", + "UnsignedLongLong", + "Float", + "Double", + "Pointer", + "Int8", + "UnsignedInt8", + "Int16", + "UnsignedInt16", + "Int32", + "UnsignedInt32", + "Int64", + "UnsignedInt64", +] + + +NATIVE_TYPES: Dict[str, "AbstractNativeType"] = {} + + +def get_native_type(type_: str) -> "AbstractNativeType": + """ + Get a base data type by name + + Args: + type_: data type + + Returns: + class: data type class + """ + try: + return NATIVE_TYPES[type_] + except KeyError: + raise KeyError(f"Unknown type `{type_}`") + + +class NativeTypeMeta(ABCMeta): + __size__: int = 0 + " Size in bytes " + type_name: str = "" + " Type name " + native_format: str = "" + " Type format " + + def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]: + if namespace.get('native_format'): + native_format = namespace['native_format'] + namespace['__size__'] = struct.calcsize(native_format) + else: + native_format = None + namespace['native_format'] = None + namespace['__size__'] = None + new_class: Type[Any] = super().__new__(metacls, name, bases, namespace) + if namespace.get('type_name'): + NATIVE_TYPES[namespace['type_name']] = new_class + return new_class + + def __len__(cls) -> int: + "Type size (in bytes)" + return cls.__size__ + + @property + def size(cls) -> int: + "Type size (in bytes)" + return cls.__size__ + + +class AbstractNativeType(metaclass=NativeTypeMeta): + def __str__(self) -> str: + return self.type_name + + @classmethod + def sizeof(cls) -> int: + "Type size (in bytes)" + return cls.__size__ + + +class Char(AbstractNativeType): + type_name = "char" + native_format = "s" + + +class SignedChar(AbstractNativeType): + type_name = "signed char" + native_format = "b" + + +class UnsignedChar(AbstractNativeType): + type_name = "unsigned char" + native_format = "B" + + +class Short(AbstractNativeType): + type_name = "short" + native_format = "h" + + +class UnsignedShort(AbstractNativeType): + type_name = "unsigned short" + native_format = "H" + + +class Int(AbstractNativeType): + type_name = "int" + native_format = "i" + + +class UnsignedInt(AbstractNativeType): + type_name = "unsigned int" + native_format = "I" + + +class Long(AbstractNativeType): + type_name = "long" + native_format = "l" + + +class UnsignedLong(AbstractNativeType): + type_name = "unsigned long" + native_format = "L" + + +class LongLong(AbstractNativeType): + type_name = "long long" + native_format = "q" + + +class UnsignedLongLong(AbstractNativeType): + type_name = "unsigned long long" + native_format = "Q" + + +class Float(AbstractNativeType): + type_name = "float" + native_format = "f" + + +class Double(AbstractNativeType): + type_name = "double" + native_format = "d" + + +class Pointer(AbstractNativeType): + type_name = "void *" + native_format = "P" + + +class Int8(AbstractNativeType): + type_name = "int8" + native_format = "b" + + +class UnsignedInt8(AbstractNativeType): + type_name = "uint8" + native_format = "B" + + +class Int16(AbstractNativeType): + type_name = "int16" + native_format = "h" + + +class UnsignedInt16(AbstractNativeType): + type_name = "uint16" + native_format = "H" + + +class Int32(AbstractNativeType): + type_name = "int32" + native_format = "i" + + +class UnsignedInt32(AbstractNativeType): + type_name = "uint32" + native_format = "I" + + +class Int64(AbstractNativeType): + type_name = "int64" + native_format = "q" + + +class UnsignedInt64(AbstractNativeType): + type_name = "uint64" + native_format = "Q" diff --git a/docs/api/native_types.md b/docs/api/native_types.md new file mode 100644 index 0000000..d405a28 --- /dev/null +++ b/docs/api/native_types.md @@ -0,0 +1 @@ +::: cstruct.native_types diff --git a/mkdocs.yml b/mkdocs.yml index 45441d3..435ebbe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,6 +32,7 @@ nav: - "cstruct.cstruct": api/cstruct.md - "cstruct.field": api/field.md - "cstruct.mem_cstruct": api/mem_cstruct.md + - "cstruct.native_types": api/native_types.md markdown_extensions: - markdown_include.include: diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index 66e6bca..57e9557 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -263,6 +263,7 @@ def test_invalid_inline(): with pytest.raises(ParserError): cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) + def test_invalid_inline_reserved(): with pytest.raises(ParserError): cstruct.CStruct.parse('struct { int size; }') diff --git a/tests/test_get_type.py b/tests/test_get_type.py new file mode 100644 index 0000000..72cb159 --- /dev/null +++ b/tests/test_get_type.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# ***************************************************************************** +# +# Copyright (c) 2013-2019 Andrea Bonomi +# +# Published under the terms of the MIT license. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# ***************************************************************************** + +import pytest +import cstruct + + +def test_get_type(): + S2 = cstruct.parse( + """ + #define ACTIVE_FLAG 0x80 + + struct S1 { + unsigned char head; + unsigned char sector; + unsigned char cyl; + }; + typedef struct S1 S1; + + union U1 { + unsigned int a; + float b; + }; + typedef union U1 U1type; + + #define NONE 0 + + enum htmlfont { + HTMLFONT_NONE = NONE, + HTMLFONT_BOLD, + HTMLFONT_ITALIC, + }; + + struct S2 { + unsigned char status; /* 0x80 - active */ + struct S1 start; + unsigned char partition_type; + S1 end; + }; + + typedef struct S2 S2type; + """ + ) + assert S2 + assert cstruct.get_type("struct S1") + assert cstruct.get_type("S1") + assert cstruct.get_type("union U1") + assert cstruct.get_type("U1type") + assert cstruct.get_type("enum htmlfont") + assert cstruct.get_type("struct S2") + assert cstruct.get_type("int") + assert cstruct.get_type("unsigned int") + assert cstruct.get_type("long long") + with pytest.raises(KeyError): + cstruct.get_type("struct X") + with pytest.raises(KeyError): + cstruct.get_type("U1") + assert cstruct.sizeof("union U1") == max(cstruct.sizeof("unsigned int"), cstruct.sizeof("float")) diff --git a/tests/test_memcstruct.py b/tests/test_memcstruct.py index 047b9cb..e5c1dca 100644 --- a/tests/test_memcstruct.py +++ b/tests/test_memcstruct.py @@ -257,6 +257,7 @@ def test_invalid_inline(): with pytest.raises(ParserError): cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) + def test_invalid_inline_reserved(): with pytest.raises(ParserError): cstruct.MemCStruct.parse('struct { int size; }') diff --git a/tests/test_typdef.py b/tests/test_typdef.py index d02eaa3..9990f8b 100644 --- a/tests/test_typdef.py +++ b/tests/test_typdef.py @@ -28,6 +28,7 @@ from cstruct import MemCStruct, NATIVE_ORDER from cstruct.base import TYPEDEFS + class ExitStatus(MemCStruct): __def__ = """ struct ExitStatus { @@ -36,6 +37,7 @@ class ExitStatus(MemCStruct): } """ + class Utmp(MemCStruct): __byte_order__ = NATIVE_ORDER __def__ = """ @@ -66,6 +68,7 @@ class Utmp(MemCStruct): } """ + def test_typedef(): assert TYPEDEFS['pid_t'] == 'int' assert TYPEDEFS['time_t'] == 'long' From c68c9a4eb8daca5321c8bf94b51a89a7830a6b30 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Fri, 11 Nov 2022 08:31:36 +0000 Subject: [PATCH 43/78] sizeof enum --- cstruct/cenum.py | 5 ++++- tests/test_cenum.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cstruct/cenum.py b/cstruct/cenum.py index 7929f03..83bfedc 100644 --- a/cstruct/cenum.py +++ b/cstruct/cenum.py @@ -2,4 +2,7 @@ class CEnum(AbstractCEnum): - ... + @classmethod + def sizeof(cls) -> int: + "Type size (in bytes)" + return cls.__size__ diff --git a/tests/test_cenum.py b/tests/test_cenum.py index 7e84c7a..37b09bb 100644 --- a/tests/test_cenum.py +++ b/tests/test_cenum.py @@ -21,7 +21,7 @@ class HtmlFont(CEnum): __def__ = """ #define NONE 0 - enum htmlfont { + enum HtmlFont { HTMLFONT_NONE = NONE, HTMLFONT_BOLD, HTMLFONT_ITALIC, @@ -87,4 +87,14 @@ def test_struct_with_enum(): s1 = StructWithEnum() s1.unpack(packed) assert s1.font == HtmlFont.HTMLFONT_NONE - assert s1.font_size == 20 + + +def test_sizeof(): + assert cstruct.sizeof("enum Dummy") == 4 + assert cstruct.sizeof("enum HtmlFont") == 2 + + +# def test_type(): +# color = cstruct.parse("enum Color : short { red, green, blue };") +# assert color.__size__ == 2 +# assert cstruct.sizeof("enum Color") == 2 From 7963a3ba56fdfc312d30640cea71f889680f1898 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Fri, 11 Nov 2022 17:58:44 +0000 Subject: [PATCH 44/78] support enum with explicit type (C++11) --- cstruct/__init__.py | 1 + cstruct/abstract.py | 23 ++++++++++++++++++----- cstruct/c_parser.py | 31 +++++++++++++++++++++++++++---- cstruct/field.py | 7 ++----- tests/test_cenum.py | 14 ++++++++++---- 5 files changed, 58 insertions(+), 18 deletions(-) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 8af6fcc..2b21464 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -182,6 +182,7 @@ def parse( if __cls__ is None: __cls__ = MemCStruct cls_def = parse_struct_def(__struct__, __cls__=__cls__, process_muliple_definition=True, **kargs) + print('!!!!!', cls_def, kargs) if cls_def is None: return None return cls_def['__cls__'].parse(cls_def, **kargs) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index b1f4d6e..6bdd5b0 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -30,10 +30,12 @@ import hashlib from io import StringIO from enum import IntEnum, EnumMeta, _EnumDict -from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE +import struct +from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE, ENUM_SIZE_TO_C_TYPE from .c_parser import parse_struct, parse_struct_def, parse_enum_def, parse_enum, Tokens from .field import calculate_padding, FieldType -from .exceptions import CStructException +from .native_types import get_native_type +from .exceptions import CStructException, ParserError __all__ = ['CStructMeta', 'AbstractCStruct', 'CEnumMeta', 'AbstractCEnum'] @@ -315,11 +317,19 @@ def __prepare__(metacls, cls, bases, **kwds): def __new__(metacls: Type["CEnumMeta"], cls: str, bases: Tuple[Type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta": inst = super().__new__(metacls, cls, bases, classdict, **kwds) - if len(inst) > 0: - if "__size__" not in classdict: + if classdict.get("__native_format__"): # data type specified + inst.__size__ = struct.calcsize(classdict["__native_format__"]) + elif "__size__" in classdict: # size specified + try: + inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format + except KeyError: + raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}") + else: # default inst.__size__ = DEFAULT_ENUM_SIZE + inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format print(f"Warning: __size__ not specified for enum {cls}. Will default to {DEFAULT_ENUM_SIZE} bytes") + if not classdict.get("__anonymous__", False): ENUMS[cls] = inst return inst @@ -341,6 +351,7 @@ def parse( __enum__: Union[str, Tokens, Dict[str, Any]], __name__: Optional[str] = None, __size__: Optional[int] = None, + __native_format__: Optional[str] = None, **kargs: Dict[str, Any], ) -> Type["AbstractCEnum"]: """ @@ -350,14 +361,16 @@ def parse( __enum__: Definition of the enum in C syntax __name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated __size__: Number of bytes that the enum should be read as + __native_format__: struct module format Returns: cls: A new class mapping the definition """ - cls_kargs: Dict[str, Any] = dict(kargs) if __size__ is not None: cls_kargs['__size__'] = __size__ + if __native_format__ is not None: + cls_kargs['__native_format__'] = __native_format__ if isinstance(__enum__, (str, Tokens)): cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs)) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index e9b4591..1f98b89 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -29,6 +29,7 @@ from .field import calculate_padding, Kind, FieldType from .c_expr import c_eval from .exceptions import CStructException, ParserError +from .native_types import get_native_type if TYPE_CHECKING: from .abstract import AbstractCStruct, AbstractCEnum @@ -58,6 +59,16 @@ def __init__(self, text: str) -> None: def pop(self) -> str: return self.tokens.pop(0) + def pop_c_type(self) -> str: + c_type = self.pop() + if c_type in ['signed', 'unsigned'] and len(self) > 1: + # short int, long int, or long long + c_type = c_type + " " + self.pop() + elif c_type in ['short', 'long'] and len(self) > 1 and self.get() in ['int', 'long']: + # short int, long int, or long long + c_type = c_type + " " + self.pop() + return c_type + def get(self) -> str: return self.tokens[0] @@ -203,11 +214,16 @@ def parse_struct_def( if result: result['__cls__'].parse(result, **kargs) name = tokens.pop() + native_format = None + if tokens.get() == ':': # enumeration type declaration + tokens.pop() # pop ":" + type_ = get_native_type(tokens.pop_c_type()) + native_format = type_.native_format if tokens.get() == '{': # named enum tokens.pop() # pop "{" - result = parse_enum(tokens, __name__=name) + result = parse_enum(tokens, __name__=name, native_format=native_format) elif name == '{': # unnamed enum - result = parse_enum(tokens) + result = parse_enum(tokens, native_format=native_format) else: raise ParserError(f"{name} definition expected") @@ -244,9 +260,14 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s raise ParserError(f"enum expected - {kind}") name = tokens.pop() + native_format = None + if tokens.get() == ':': # enumeration type declaration + tokens.pop() # pop ":" + type_ = get_native_type(tokens.pop_c_type()) + native_format = type_.native_format if tokens.get() == '{': # named enum tokens.pop() # pop "{" - return parse_enum(tokens, __name__=name) + return parse_enum(tokens, __name__=name, native_format=native_format) elif name == '{': # unnamed enum return parse_enum(tokens) else: @@ -256,6 +277,7 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s def parse_enum( __enum__: Union[str, Tokens], __name__: Optional[str] = None, + native_format: Optional[str] = None, **kargs: Any, ) -> Optional[Dict[str, Any]]: """ @@ -264,6 +286,7 @@ def parse_enum( Args: __enum__: definition of the enum in C syntax __name__: enum name + native_format: struct module format Returns: dict: the parsed definition @@ -283,7 +306,6 @@ def parse_enum( break name = tokens.pop() - next_token = tokens.pop() if next_token in {",", "}"}: # enum-constant without explicit value if len(constants) == 0: @@ -324,6 +346,7 @@ def parse_enum( '__is_union__': False, '__is_enum__': True, '__name__': __name__, + '__native_format__': native_format, '__cls__': CEnum, } return result diff --git a/cstruct/field.py b/cstruct/field.py index 1533a2a..957b1d3 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -26,7 +26,7 @@ import struct from enum import Enum from typing import Optional, Any, List, Type, TYPE_CHECKING -from .base import NATIVE_ORDER, ENUM_SIZE_TO_C_TYPE +from .base import NATIVE_ORDER from .native_types import get_native_type from .exceptions import ParserError @@ -193,10 +193,7 @@ def native_format(self) -> str: except KeyError: raise ParserError(f"Unknow type `{self.c_type}`") elif self.is_enum: - try: - return get_native_type(ENUM_SIZE_TO_C_TYPE[self.ref.size]).native_format - except KeyError: - raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}") + return self.ref.__native_format__ else: return 'c' diff --git a/tests/test_cenum.py b/tests/test_cenum.py index 37b09bb..5823a12 100644 --- a/tests/test_cenum.py +++ b/tests/test_cenum.py @@ -29,6 +29,12 @@ class HtmlFont(CEnum): """ +class EnumWithType(CEnum): + __def__ = """ + enum EnumWithType : int { a, b, c, d}; + """ + + class StructWithEnum(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN __def__ = """ @@ -94,7 +100,7 @@ def test_sizeof(): assert cstruct.sizeof("enum HtmlFont") == 2 -# def test_type(): -# color = cstruct.parse("enum Color : short { red, green, blue };") -# assert color.__size__ == 2 -# assert cstruct.sizeof("enum Color") == 2 +def test_type(): + color = cstruct.parse("enum Color : unsigned short { red, green, blue };") + assert color.__size__ == 2 + assert cstruct.sizeof("enum Color") == 2 From d99e99ae803fabdda6dfd4a270ae15116179ed53 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Fri, 11 Nov 2022 18:39:20 +0000 Subject: [PATCH 45/78] tokenizer --- cstruct/c_parser.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 1f98b89..eb00b34 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -24,7 +24,7 @@ import re from collections import OrderedDict -from typing import Union, Optional, Any, Dict, Type, TYPE_CHECKING +from typing import Union, Optional, Any, Dict, List, Type, TYPE_CHECKING from .base import DEFINES, ENUMS, TYPEDEFS, STRUCTS from .field import calculate_padding, Kind, FieldType from .c_expr import c_eval @@ -36,6 +36,8 @@ __all__ = ['parse_struct', 'parse_struct_def', 'parse_enum_def', 'Tokens'] +SEPARATORS = [" ", "\t", "\n", ";", "{", "}", ":", ",", "="] +SPACES = [" ", "\t", "\n"] class Tokens(object): def __init__(self, text: str) -> None: @@ -52,9 +54,24 @@ def __init__(self, text: str) -> None: raise ParserError(f"Parsing line {line}") else: lines.append(line) - text = " ".join(lines) - text = text.replace(";", " ; ").replace("{", " { ").replace("}", " } ").replace(",", " , ").replace("=", " = ") - self.tokens = text.split() + text = "\n".join(lines) + self.tokens = self.tokenize(text) + + def tokenize(self, text) -> List[str]: + tokens: List[str] = [] + t: List[str] = [] + for c in text: + if c in SEPARATORS: + if t: + tokens.append("".join(t)) + t.clear() + if c not in SPACES: + tokens.append(c) + else: + t.append(c) + if t: + tokens.append(t.getvalue()) + return tokens def pop(self) -> str: return self.tokens.pop(0) From f7b037272b0f49800eb1e6f7900d57ecfefb3c3b Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 12 Nov 2022 07:52:35 +0000 Subject: [PATCH 46/78] examples --- cstruct/__init__.py | 39 ++++++++++++++++++++++++++++++++++++++- cstruct/c_parser.py | 32 ++++++++++++++++---------------- cstruct/native_types.py | 3 +++ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 2b21464..68f168e 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -67,6 +67,9 @@ def define(key: str, value: Any) -> None: """ Define a constant that can be used in the C struct + Examples: + >>> define("INIT_THREAD_SIZE", 16384) + Args: key: identifier value: value of the constant @@ -78,8 +81,15 @@ def undef(key: str) -> None: """ Undefine a symbol that was previously defined with define + Examples: + >>> define("INIT_THREAD_SIZE", 16384) + >>> undef("INIT_THREAD_SIZE") + Args: key: identifier + + Raises: + KeyError: If key is not defined """ del DEFINES[key] @@ -88,8 +98,15 @@ def getdef(key: str) -> Any: """ Return the value for a constant + Examples: + >>> define("INIT_THREAD_SIZE", 16384) + >>> getdef("INIT_THREAD_SIZE") + Args: key: identifier + + Raises: + KeyError: If key is not defined """ return DEFINES[key] @@ -98,6 +115,11 @@ def typedef(type_: str, alias: str) -> None: """ Define an alias name for a data type + Examples: + >>> typedef("int", "status") + >>> sizeof("status") + 4 + Args: type_: data type alias: new alias name @@ -120,6 +142,9 @@ def get_type(type_: str) -> Any: Returns: class: data type class + + Raises: + KeyError: If type is not defined """ while type_ in TYPEDEFS: type_ = TYPEDEFS[type_] @@ -148,6 +173,8 @@ def sizeof(type_: str) -> int: Examples: >>> sizeof("struct Position") 16 + >>> sizeof('enum htmlfont') + 4 >>> sizeof("int") 4 @@ -156,6 +183,9 @@ def sizeof(type_: str) -> int: Returns: size: size in bytes + + Raises: + KeyError: If type is not defined """ while type_ in TYPEDEFS: type_ = TYPEDEFS[type_] @@ -169,7 +199,11 @@ def parse( """ Return a new class mapping a C struct/union/enum definition. If the string does not contains any definition, return None. + If the string contains multiple struct/union/enum definitions, returns the last definition. + Examples: + >>> cstruct.parse('struct Pair { unsigned char a; unsigned char b; };') + Args: __struct__ (str): definition of the struct (or union/enum) in C syntax @@ -178,11 +212,14 @@ def parse( Returns: cls: __cls__ subclass + + Raises: + cstruct.exceptions.ParserError: Parsing exception + """ if __cls__ is None: __cls__ = MemCStruct cls_def = parse_struct_def(__struct__, __cls__=__cls__, process_muliple_definition=True, **kargs) - print('!!!!!', cls_def, kargs) if cls_def is None: return None return cls_def['__cls__'].parse(cls_def, **kargs) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index eb00b34..3f6d0a5 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -51,7 +51,7 @@ def __init__(self, text: str) -> None: _, name, value = line.strip().split(maxsplit=2) DEFINES[name] = c_eval(value) except Exception: - raise ParserError(f"Parsing line {line}") + raise ParserError(f"Parsing line `{line}`") else: lines.append(line) text = "\n".join(lines) @@ -124,7 +124,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt if "[" in next_token: t = next_token.split("[") if len(t) != 2: - raise ParserError("Error parsing: " + next_token) + raise ParserError(f"Error parsing: `{next_token}`") next_token = t[0].strip() vlen_part = t[1] vlen_expr = [] @@ -163,7 +163,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt try: ref = STRUCTS[tail] except KeyError: - raise ParserError(f"Unknown '{c_type}' '{tail}'") + raise ParserError(f"Unknown `{c_type} {tail}`") elif c_type.startswith('enum'): from .cenum import CEnum @@ -181,7 +181,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt try: ref = ENUMS[tail] except KeyError: - raise ParserError(f"Unknown '{c_type}' '{tail}'") + raise ParserError(f"Unknown `{c_type} {tail}`") else: # other types kind = Kind.NATIVE ref = None @@ -201,7 +201,7 @@ def parse_typedef(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: TYPEDEFS[vname] = f"struct {field_type.ref.__name__}" t = tokens.pop() if t != ';': - raise ParserError(f"; expected but {t} found") + raise ParserError(f"`;` expected but `{t}` found") def parse_struct_def( @@ -242,7 +242,7 @@ def parse_struct_def( elif name == '{': # unnamed enum result = parse_enum(tokens, native_format=native_format) else: - raise ParserError(f"{name} definition expected") + raise ParserError(f"`{name}` definition expected") elif kind in ['struct', 'union']: if result: @@ -257,10 +257,10 @@ def parse_struct_def( tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__, __name__=name ) else: - raise ParserError(f"{name} definition expected") + raise ParserError(f"`{name}` definition expected") else: - raise ParserError(f"struct, union, or enum expected - {kind}") + raise ParserError(f"struct, union, or enum expected - `{kind}` found") return result @@ -274,7 +274,7 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s return None kind = tokens.pop() if kind not in ['enum']: - raise ParserError(f"enum expected - {kind}") + raise ParserError(f"enum expected - `{kind}` found") name = tokens.pop() native_format = None @@ -288,7 +288,7 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s elif name == '{': # unnamed enum return parse_enum(tokens) else: - raise ParserError(f"{name} definition expected") + raise ParserError(f"`{name}` definition expected") def parse_enum( @@ -348,10 +348,10 @@ def parse_enum( except (ValueError, TypeError): value = int(int_expr) else: - raise ParserError(f"{__enum__} is not a valid enum expression") + raise ParserError(f"`{__enum__}` is not a valid enum expression") if name in constants: - raise ParserError(f"duplicate enum name {name}") + raise ParserError(f"duplicate enum name `{name}`") constants[name] = value if next_token == "}": @@ -416,15 +416,15 @@ def parse_struct( field_type = parse_type(tokens, __cls__, __byte_order__, offset) vname = tokens.pop() if vname in fields_types: - raise ParserError(f"Duplicate member '{vname}'") + raise ParserError(f"Duplicate member `{vname}`") if vname in dir(__cls__): - raise ParserError(f"Invalid reserved member name '{vname}'") + raise ParserError(f"Invalid reserved member name `{vname}`") # anonymous nested union if vname == ';' and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): # add the anonymous struct fields to the parent for nested_field_name, nested_field_type in field_type.ref.__fields_types__.items(): if nested_field_name in fields_types: - raise ParserError(f"Duplicate member '{nested_field_name}'") + raise ParserError(f"Duplicate member `{nested_field_name}`") fields_types[nested_field_name] = nested_field_type vname = f"__anonymous{anonymous}" anonymous += 1 @@ -438,7 +438,7 @@ def parse_struct( offset = field_type.offset + field_type.vsize t = tokens.pop() if t != ';': - raise ParserError(f"; expected but {t} found") + raise ParserError(f"`;` expected but `{t}` found") if __is_union__: # C union # Calculate the sizeof union as size of its largest element diff --git a/cstruct/native_types.py b/cstruct/native_types.py index 517930d..46cf011 100644 --- a/cstruct/native_types.py +++ b/cstruct/native_types.py @@ -66,6 +66,9 @@ def get_native_type(type_: str) -> "AbstractNativeType": Returns: class: data type class + + Raises: + KeyError: If type is not defined """ try: return NATIVE_TYPES[type_] From 8d522678d60c5d1765e174e0da254e1748bc769b Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 12 Nov 2022 07:57:25 +0000 Subject: [PATCH 47/78] Black code style --- cstruct/__init__.py | 46 +++++++------- cstruct/abstract.py | 50 +++++++-------- cstruct/base.py | 54 ++++++++-------- cstruct/c_expr.py | 2 +- cstruct/c_parser.py | 119 ++++++++++++++++++----------------- cstruct/field.py | 10 +-- cstruct/mem_cstruct.py | 2 +- cstruct/native_types.py | 14 ++--- tests/test_alignment.py | 36 +++++------ tests/test_c_expr.py | 4 +- tests/test_cstruct.py | 20 +++--- tests/test_define.py | 28 ++++----- tests/test_flexible_array.py | 88 +++++++++++++------------- tests/test_memcstruct.py | 20 +++--- tests/test_nested.py | 6 +- tests/test_typdef.py | 8 +-- tests/test_union.py | 6 +- 17 files changed, 257 insertions(+), 256 deletions(-) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 68f168e..a20e843 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -22,10 +22,10 @@ # IN THE SOFTWARE. # -__author__ = 'Andrea Bonomi ' -__license__ = 'MIT' -__version__ = '4.0' -__date__ = '15 August 2013' +__author__ = "Andrea Bonomi " +__license__ = "MIT" +__version__ = "4.0" +__date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union from .base import ( @@ -46,20 +46,20 @@ from .native_types import get_native_type __all__ = [ - 'LITTLE_ENDIAN', - 'BIG_ENDIAN', - 'NATIVE_ORDER', - 'CHAR_ZERO', - 'CStruct', - 'MemCStruct', - 'CEnum', - 'define', - 'undef', - 'getdef', - 'typedef', - 'get_type', - 'sizeof', - 'parse', + "LITTLE_ENDIAN", + "BIG_ENDIAN", + "NATIVE_ORDER", + "CHAR_ZERO", + "CStruct", + "MemCStruct", + "CEnum", + "define", + "undef", + "getdef", + "typedef", + "get_type", + "sizeof", + "parse", ] @@ -150,14 +150,14 @@ def get_type(type_: str) -> Any: type_ = TYPEDEFS[type_] if isinstance(type_, CStructMeta): return type_ - elif type_.startswith('struct ') or type_.startswith('union '): - kind, type_ = type_.split(' ', 1) + elif type_.startswith("struct ") or type_.startswith("union "): + kind, type_ = type_.split(" ", 1) try: return STRUCTS[type_] except KeyError: raise KeyError(f"Unknown {kind} `{type_}`") - elif type_.startswith('enum '): - kind, type_ = type_.split(' ', 1) + elif type_.startswith("enum "): + kind, type_ = type_.split(" ", 1) try: return ENUMS[type_] except KeyError: @@ -222,4 +222,4 @@ def parse( cls_def = parse_struct_def(__struct__, __cls__=__cls__, process_muliple_definition=True, **kargs) if cls_def is None: return None - return cls_def['__cls__'].parse(cls_def, **kargs) + return cls_def["__cls__"].parse(cls_def, **kargs) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 6bdd5b0..d97bca5 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -37,27 +37,27 @@ from .native_types import get_native_type from .exceptions import CStructException, ParserError -__all__ = ['CStructMeta', 'AbstractCStruct', 'CEnumMeta', 'AbstractCEnum'] +__all__ = ["CStructMeta", "AbstractCStruct", "CEnumMeta", "AbstractCEnum"] class CStructMeta(ABCMeta): __size__: int = 0 def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]: - __struct__ = namespace.get('__struct__', None) - namespace['__cls__'] = bases[0] if bases else None + __struct__ = namespace.get("__struct__", None) + namespace["__cls__"] = bases[0] if bases else None # Parse the struct - if '__struct__' in namespace: - if isinstance(namespace['__struct__'], (str, Tokens)): + if "__struct__" in namespace: + if isinstance(namespace["__struct__"], (str, Tokens)): namespace.update(parse_struct(**namespace)) __struct__ = True - if '__def__' in namespace: + if "__def__" in namespace: namespace.update(parse_struct_def(**namespace)) __struct__ = True # Create the new class new_class: Type[Any] = super().__new__(metacls, name, bases, namespace) # Register the class - if __struct__ is not None and not namespace.get('__anonymous__'): + if __struct__ is not None and not namespace.get("__anonymous__"): STRUCTS[name] = new_class return new_class @@ -128,23 +128,23 @@ def parse( """ cls_kargs: Dict[str, Any] = dict(kargs) if __byte_order__ is not None: - cls_kargs['__byte_order__'] = __byte_order__ + cls_kargs["__byte_order__"] = __byte_order__ if __is_union__ is not None: - cls_kargs['__is_union__'] = __is_union__ - cls_kargs['__struct__'] = __struct__ + cls_kargs["__is_union__"] = __is_union__ + cls_kargs["__struct__"] = __struct__ if isinstance(__struct__, (str, Tokens)): - del cls_kargs['__struct__'] + del cls_kargs["__struct__"] cls_kargs.update(parse_struct_def(__struct__, __cls__=cls, **cls_kargs)) - cls_kargs['__struct__'] = None + cls_kargs["__struct__"] = None elif isinstance(__struct__, dict): - del cls_kargs['__struct__'] + del cls_kargs["__struct__"] cls_kargs.update(__struct__) - cls_kargs['__struct__'] = None - __name__ = cls_kargs.get('__name__') or __name__ + cls_kargs["__struct__"] = None + __name__ = cls_kargs.get("__name__") or __name__ if __name__ is None: # Anonymous struct - __name__ = cls.__name__ + '_' + hashlib.sha1(str(__struct__).encode('utf-8')).hexdigest() - cls_kargs['__anonymous__'] = True - cls_kargs['__name__'] = __name__ + __name__ = cls.__name__ + "_" + hashlib.sha1(str(__struct__).encode("utf-8")).hexdigest() + cls_kargs["__anonymous__"] = True + cls_kargs["__name__"] = __name__ return type(__name__, (cls,), cls_kargs) def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> None: @@ -173,7 +173,7 @@ def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length flexible_array_length: flexible array length """ self.set_flexible_array_length(flexible_array_length) - if hasattr(buffer, 'read'): + if hasattr(buffer, "read"): buffer = buffer.read(self.size) # type: ignore if not buffer: return False @@ -237,7 +237,7 @@ def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = No end_addr: end address """ buffer = StringIO() - if hasattr(self, '__mem__'): + if hasattr(self, "__mem__"): mem = self.__mem__ else: mem = self.pack() @@ -245,11 +245,11 @@ def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = No row = mem[i : i + 16] buffer.write(f"{i:08x} ") for j, c in enumerate(row): - separator = ' ' if j == 7 else '' + separator = " " if j == 7 else "" buffer.write(f" {c:02x}{separator}") buffer.write(" |") for c in row: - buffer.write(chr(c) if c >= 32 and c < 127 else '.') + buffer.write(chr(c) if c >= 32 and c < 127 else ".") buffer.write("|") buffer.write("\n") buffer.seek(0, 0) @@ -368,16 +368,16 @@ def parse( """ cls_kargs: Dict[str, Any] = dict(kargs) if __size__ is not None: - cls_kargs['__size__'] = __size__ + cls_kargs["__size__"] = __size__ if __native_format__ is not None: - cls_kargs['__native_format__'] = __native_format__ + cls_kargs["__native_format__"] = __native_format__ if isinstance(__enum__, (str, Tokens)): cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs)) elif isinstance(__enum__, dict): cls_kargs.update(__enum__) - __name__ = cls_kargs.get('__name__') or __name__ + __name__ = cls_kargs.get("__name__") or __name__ if __name__ is None: __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest() cls_kargs["__anonymous__"] = True diff --git a/cstruct/base.py b/cstruct/base.py index d764c85..1ee83b3 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -28,22 +28,22 @@ from .abstract import AbstractCStruct, AbstractCEnum __all__ = [ - 'LITTLE_ENDIAN', - 'BIG_ENDIAN', - 'NATIVE_ORDER', - 'CHAR_ZERO', - 'STRUCTS', - 'DEFINES', - 'TYPEDEFS', - 'CHAR_ZERO', - 'DEFAULT_ENUM_SIZE', + "LITTLE_ENDIAN", + "BIG_ENDIAN", + "NATIVE_ORDER", + "CHAR_ZERO", + "STRUCTS", + "DEFINES", + "TYPEDEFS", + "CHAR_ZERO", + "DEFAULT_ENUM_SIZE", ] -LITTLE_ENDIAN = '<' +LITTLE_ENDIAN = "<" "Little-endian, std. size & alignment" -BIG_ENDIAN = '>' +BIG_ENDIAN = ">" "Big-endian, std. size & alignment" -NATIVE_ORDER = '@' +NATIVE_ORDER = "@" "Native order, size & alignment" STRUCTS: Dict[str, Type["AbstractCStruct"]] = {} @@ -53,23 +53,23 @@ DEFINES: Dict[str, Any] = {} TYPEDEFS: Dict[str, str] = { - 'short int': 'short', - 'unsigned short int': 'unsigned short', - 'ushort': 'unsigned short', - 'long int': 'long', - 'unsigned long int': 'unsigned long', - 'int8_t': 'int8', - 'uint8_t': 'uint8', - 'int16_t': 'int16', - 'uint16_t': 'uint16', - 'int32_t': 'int32', - 'uint32_t': 'uint32', - 'int64_t': 'int64', - 'uint64_t': 'uint64', + "short int": "short", + "unsigned short int": "unsigned short", + "ushort": "unsigned short", + "long int": "long", + "unsigned long int": "unsigned long", + "int8_t": "int8", + "uint8_t": "uint8", + "int16_t": "int16", + "uint16_t": "uint16", + "int32_t": "int32", + "uint32_t": "uint32", + "int64_t": "int64", + "uint64_t": "uint64", } -ENUM_SIZE_TO_C_TYPE: Dict[int, str] = {1: 'int8', 2: 'int16', 4: 'int32', 8: 'int64'} +ENUM_SIZE_TO_C_TYPE: Dict[int, str] = {1: "int8", 2: "int16", 4: "int32", 8: "int64"} -CHAR_ZERO = bytes('\0', 'ascii') +CHAR_ZERO = bytes("\0", "ascii") DEFAULT_ENUM_SIZE = 4 diff --git a/cstruct/c_expr.py b/cstruct/c_expr.py index a74123d..d46406f 100644 --- a/cstruct/c_expr.py +++ b/cstruct/c_expr.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: from .abstract import AbstractCStruct -__all__ = ['c_eval'] +__all__ = ["c_eval"] def c_eval(expr: str) -> Union[int, float]: diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 3f6d0a5..16cedb5 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -34,11 +34,12 @@ if TYPE_CHECKING: from .abstract import AbstractCStruct, AbstractCEnum -__all__ = ['parse_struct', 'parse_struct_def', 'parse_enum_def', 'Tokens'] +__all__ = ["parse_struct", "parse_struct_def", "parse_enum_def", "Tokens"] SEPARATORS = [" ", "\t", "\n", ";", "{", "}", ":", ",", "="] SPACES = [" ", "\t", "\n"] + class Tokens(object): def __init__(self, text: str) -> None: # remove the comments @@ -78,10 +79,10 @@ def pop(self) -> str: def pop_c_type(self) -> str: c_type = self.pop() - if c_type in ['signed', 'unsigned'] and len(self) > 1: + if c_type in ["signed", "unsigned"] and len(self) > 1: # short int, long int, or long long c_type = c_type + " " + self.pop() - elif c_type in ['short', 'long'] and len(self) > 1 and self.get() in ['int', 'long']: + elif c_type in ["short", "long"] and len(self) > 1 and self.get() in ["int", "long"]: # short int, long int, or long long c_type = c_type + " " + self.pop() return c_type @@ -99,12 +100,12 @@ def __str__(self) -> str: return str(self.tokens) -def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Optional[str], offset: int) -> "FieldType": +def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Optional[str], offset: int) -> "FieldType": if len(tokens) < 2: raise ParserError("Parsing error") c_type = tokens.pop() # signed/unsigned/struct - if c_type in ['signed', 'unsigned', 'struct', 'union', 'enum'] and len(tokens) > 1: + if c_type in ["signed", "unsigned", "struct", "union", "enum"] and len(tokens) > 1: c_type = c_type + " " + tokens.pop() vlen = 1 @@ -113,13 +114,13 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt if not c_type.endswith("{"): next_token = tokens.pop() # short int, long int, or long long - if next_token in ['int', 'long']: + if next_token in ["int", "long"]: c_type = c_type + " " + next_token next_token = tokens.pop() # void * if next_token.startswith("*"): next_token = next_token[1:] - c_type = 'void *' + c_type = "void *" # parse length if "[" in next_token: t = next_token.split("[") @@ -148,14 +149,14 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt c_type = TYPEDEFS[c_type] # calculate fmt - if c_type.startswith('struct ') or c_type.startswith('union '): # struct/union - c_type, tail = c_type.split(' ', 1) - kind = Kind.STRUCT if c_type == 'struct' else Kind.UNION - if tokens.get() == '{': # Named nested struct + if c_type.startswith("struct ") or c_type.startswith("union "): # struct/union + c_type, tail = c_type.split(" ", 1) + kind = Kind.STRUCT if c_type == "struct" else Kind.UNION + if tokens.get() == "{": # Named nested struct tokens.push(tail) tokens.push(c_type) ref: Union[Type[AbstractCEnum], Type[AbstractCStruct]] = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) - elif tail == '{': # Unnamed nested struct + elif tail == "{": # Unnamed nested struct tokens.push(tail) tokens.push(c_type) ref = __cls__.parse(tokens, __byte_order__=byte_order) @@ -164,16 +165,16 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt ref = STRUCTS[tail] except KeyError: raise ParserError(f"Unknown `{c_type} {tail}`") - elif c_type.startswith('enum'): + elif c_type.startswith("enum"): from .cenum import CEnum - c_type, tail = c_type.split(' ', 1) + c_type, tail = c_type.split(" ", 1) kind = Kind.ENUM - if tokens.get() == '{': # Named nested struct + if tokens.get() == "{": # Named nested struct tokens.push(tail) tokens.push(c_type) ref = CEnum.parse(tokens, __name__=tail) - elif tail == '{': # unnamed nested struct + elif tail == "{": # unnamed nested struct tokens.push(tail) tokens.push(c_type) ref = CEnum.parse(tokens) @@ -188,7 +189,7 @@ def parse_type(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Opt return FieldType(kind, c_type, ref, vlen, flexible_array, byte_order, offset) -def parse_typedef(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: Optional[str]) -> None: +def parse_typedef(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Optional[str]) -> None: field_type = parse_type(tokens, __cls__, byte_order, 0) vname = tokens.pop() if field_type.ref is None: @@ -200,13 +201,13 @@ def parse_typedef(tokens: Tokens, __cls__: Type['AbstractCStruct'], byte_order: else: TYPEDEFS[vname] = f"struct {field_type.ref.__name__}" t = tokens.pop() - if t != ';': + if t != ";": raise ParserError(f"`;` expected but `{t}` found") def parse_struct_def( __def__: Union[str, Tokens], - __cls__: Type['AbstractCStruct'], + __cls__: Type["AbstractCStruct"], __byte_order__: Optional[str] = None, process_muliple_definition: bool = False, **kargs: Any, # Type['AbstractCStruct'], @@ -219,39 +220,39 @@ def parse_struct_def( result = None while tokens and (process_muliple_definition or not result): kind = tokens.pop() - if kind == ';': + if kind == ";": pass - elif kind == 'typedef': + elif kind == "typedef": if result: - result['__cls__'].parse(result, **kargs) + result["__cls__"].parse(result, **kargs) parse_typedef(tokens, __cls__, __byte_order__) - elif kind == 'enum': + elif kind == "enum": if result: - result['__cls__'].parse(result, **kargs) + result["__cls__"].parse(result, **kargs) name = tokens.pop() native_format = None - if tokens.get() == ':': # enumeration type declaration + if tokens.get() == ":": # enumeration type declaration tokens.pop() # pop ":" type_ = get_native_type(tokens.pop_c_type()) native_format = type_.native_format - if tokens.get() == '{': # named enum + if tokens.get() == "{": # named enum tokens.pop() # pop "{" result = parse_enum(tokens, __name__=name, native_format=native_format) - elif name == '{': # unnamed enum + elif name == "{": # unnamed enum result = parse_enum(tokens, native_format=native_format) else: raise ParserError(f"`{name}` definition expected") - elif kind in ['struct', 'union']: + elif kind in ["struct", "union"]: if result: - result['__cls__'].parse(result, **kargs) - __is_union__ = kind == 'union' + result["__cls__"].parse(result, **kargs) + __is_union__ = kind == "union" name = tokens.pop() - if name == '{': # unnamed nested struct + if name == "{": # unnamed nested struct result = parse_struct(tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__) - elif tokens.get() == '{': # Named nested struct + elif tokens.get() == "{": # Named nested struct tokens.pop() # pop "{" result = parse_struct( tokens, __cls__=__cls__, __is_union__=__is_union__, __byte_order__=__byte_order__, __name__=name @@ -273,19 +274,19 @@ def parse_enum_def(__def__: Union[str, Tokens], **kargs: Any) -> Optional[Dict[s if not tokens: return None kind = tokens.pop() - if kind not in ['enum']: + if kind not in ["enum"]: raise ParserError(f"enum expected - `{kind}` found") name = tokens.pop() native_format = None - if tokens.get() == ':': # enumeration type declaration + if tokens.get() == ":": # enumeration type declaration tokens.pop() # pop ":" type_ = get_native_type(tokens.pop_c_type()) native_format = type_.native_format - if tokens.get() == '{': # named enum + if tokens.get() == "{": # named enum tokens.pop() # pop "{" return parse_enum(tokens, __name__=name, native_format=native_format) - elif name == '{': # unnamed enum + elif name == "{": # unnamed enum return parse_enum(tokens) else: raise ParserError(f"`{name}` definition expected") @@ -318,7 +319,7 @@ def parse_enum( tokens = Tokens(__enum__) while len(tokens): - if tokens.get() == '}': + if tokens.get() == "}": tokens.pop() break @@ -358,20 +359,20 @@ def parse_enum( break result = { - '__constants__': constants, - '__is_struct__': False, - '__is_union__': False, - '__is_enum__': True, - '__name__': __name__, - '__native_format__': native_format, - '__cls__': CEnum, + "__constants__": constants, + "__is_struct__": False, + "__is_union__": False, + "__is_enum__": True, + "__name__": __name__, + "__native_format__": native_format, + "__cls__": CEnum, } return result def parse_struct( __struct__: Union[str, Tokens], - __cls__: Type['AbstractCStruct'], + __cls__: Type["AbstractCStruct"], __is_union__: bool = False, __byte_order__: Optional[str] = None, __name__: Optional[str] = None, @@ -407,7 +408,7 @@ def parse_struct( else: tokens = Tokens(__struct__) while len(tokens): - if tokens.get() == '}': + if tokens.get() == "}": tokens.pop() break # flexible array member must be the last member of such a struct @@ -420,7 +421,7 @@ def parse_struct( if vname in dir(__cls__): raise ParserError(f"Invalid reserved member name `{vname}`") # anonymous nested union - if vname == ';' and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): + if vname == ";" and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): # add the anonymous struct fields to the parent for nested_field_name, nested_field_type in field_type.ref.__fields_types__.items(): if nested_field_name in fields_types: @@ -428,7 +429,7 @@ def parse_struct( fields_types[nested_field_name] = nested_field_type vname = f"__anonymous{anonymous}" anonymous += 1 - tokens.push(';') + tokens.push(";") fields_types[vname] = field_type # calculate the max field size (for the alignment) max_alignment = max(max_alignment, field_type.alignment) @@ -437,7 +438,7 @@ def parse_struct( field_type.align_filed_offset() offset = field_type.offset + field_type.vsize t = tokens.pop() - if t != ';': + if t != ";": raise ParserError(f"`;` expected but `{t}` found") if __is_union__: # C union @@ -449,15 +450,15 @@ def parse_struct( # Prepare the result result = { - '__fields__': list(fields_types.keys()), - '__fields_types__': fields_types, - '__size__': size, - '__is_struct__': not __is_union__, - '__is_union__': __is_union__, - '__is_enum__': False, - '__byte_order__': __byte_order__, - '__alignment__': max_alignment, - '__name__': __name__, - '__cls__': __cls__, + "__fields__": list(fields_types.keys()), + "__fields_types__": fields_types, + "__size__": size, + "__is_struct__": not __is_union__, + "__is_union__": __is_union__, + "__is_enum__": False, + "__byte_order__": __byte_order__, + "__alignment__": max_alignment, + "__name__": __name__, + "__cls__": __cls__, } return result diff --git a/cstruct/field.py b/cstruct/field.py index 957b1d3..bd9b3e3 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: from .abstract import AbstractCStruct -__all__ = ['align', 'calculate_padding', 'Kind', 'FieldType'] +__all__ = ["align", "calculate_padding", "Kind", "FieldType"] def align(byte_order: Optional[str]) -> bool: @@ -162,7 +162,7 @@ def pack(self, data: Any) -> bytes: @property def is_array(self) -> bool: "True if field is an array/flexible array" - return self.flexible_array or (not (self.vlen == 1 or self.c_type == 'char')) + return self.flexible_array or (not (self.vlen == 1 or self.c_type == "char")) @property def is_native(self) -> bool: @@ -195,13 +195,13 @@ def native_format(self) -> str: elif self.is_enum: return self.ref.__native_format__ else: - return 'c' + return "c" @property def fmt(self) -> str: "Field format prefixed by byte order (struct library format)" if self.is_native or self.is_enum: - fmt = (str(self.vlen) if self.vlen > 1 or self.flexible_array else '') + self.native_format + fmt = (str(self.vlen) if self.vlen > 1 or self.flexible_array else "") + self.native_format else: # Struct/Union fmt = str(self.vlen * self.ref.sizeof()) + self.native_format if self.byte_order: @@ -227,7 +227,7 @@ def alignment(self) -> int: def align_filed_offset(self) -> None: "If the byte order is native, align the field" - if align(self.byte_order) and self.c_type != 'char': + if align(self.byte_order) and self.c_type != "char": self.padding = calculate_padding(self.byte_order, self.alignment, self.base_offset) self.offset = self.base_offset + self.padding diff --git a/cstruct/mem_cstruct.py b/cstruct/mem_cstruct.py index 61f2c6a..dc2f9c3 100644 --- a/cstruct/mem_cstruct.py +++ b/cstruct/mem_cstruct.py @@ -29,7 +29,7 @@ class CStructList(List[Any]): - def __init__(self, values: List[Any], name: str, parent: Optional['MemCStruct'] = None) -> None: + def __init__(self, values: List[Any], name: str, parent: Optional["MemCStruct"] = None) -> None: super().__init__(values) self.name = name self.parent = parent diff --git a/cstruct/native_types.py b/cstruct/native_types.py index 46cf011..6a60f6c 100644 --- a/cstruct/native_types.py +++ b/cstruct/native_types.py @@ -85,16 +85,16 @@ class NativeTypeMeta(ABCMeta): " Type format " def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]: - if namespace.get('native_format'): - native_format = namespace['native_format'] - namespace['__size__'] = struct.calcsize(native_format) + if namespace.get("native_format"): + native_format = namespace["native_format"] + namespace["__size__"] = struct.calcsize(native_format) else: native_format = None - namespace['native_format'] = None - namespace['__size__'] = None + namespace["native_format"] = None + namespace["__size__"] = None new_class: Type[Any] = super().__new__(metacls, name, bases, namespace) - if namespace.get('type_name'): - NATIVE_TYPES[namespace['type_name']] = new_class + if namespace.get("type_name"): + NATIVE_TYPES[namespace["type_name"]] = new_class return new_class def __len__(cls) -> int: diff --git a/tests/test_alignment.py b/tests/test_alignment.py index b7cc3e1..609abf5 100644 --- a/tests/test_alignment.py +++ b/tests/test_alignment.py @@ -189,8 +189,8 @@ class Foo10(CStruct): def test_utmp_sizeof(): - assert Utmp.__fields_types__['ut_type'].padding == 0 - assert Utmp.__fields_types__['ut_pid'].padding == 2 + assert Utmp.__fields_types__["ut_type"].padding == 0 + assert Utmp.__fields_types__["ut_pid"].padding == 2 assert sizeof("struct Utmp") == 384 assert Utmp().size == 384 @@ -200,15 +200,15 @@ def test_utmp_sizeof(): def test_foo1_sizeof(): if IS_64BITS: - assert Foo1.__fields_types__['p'].padding == 0 - assert Foo1.__fields_types__['c'].padding == 0 - assert Foo1.__fields_types__['x'].padding == 7 + assert Foo1.__fields_types__["p"].padding == 0 + assert Foo1.__fields_types__["c"].padding == 0 + assert Foo1.__fields_types__["x"].padding == 7 assert sizeof("struct Foo1") == 24 assert Foo1().size == 24 else: - assert Foo1.__fields_types__['p'].padding == 0 - assert Foo1.__fields_types__['c'].padding == 0 - assert Foo1.__fields_types__['x'].padding == 3 + assert Foo1.__fields_types__["p"].padding == 0 + assert Foo1.__fields_types__["c"].padding == 0 + assert Foo1.__fields_types__["x"].padding == 3 assert sizeof("struct Foo1") == 12 assert Foo1().size == 12 @@ -238,27 +238,27 @@ def test_foo4_sizeof(): def test_foo5_sizeof(): if IS_64BITS: - assert Foo5.__fields_types__['c'].padding == 0 - assert Foo5.__fields_types__['inner'].padding == 7 + assert Foo5.__fields_types__["c"].padding == 0 + assert Foo5.__fields_types__["inner"].padding == 7 assert sizeof("struct Foo5") == 24 assert Foo5().size == 24 else: - assert Foo5.__fields_types__['c'].padding == 0 - assert Foo5.__fields_types__['inner'].padding == 3 + assert Foo5.__fields_types__["c"].padding == 0 + assert Foo5.__fields_types__["inner"].padding == 3 assert sizeof("struct Foo5") == 12 assert Foo5().size == 12 def test_foo10_sizeof(): if IS_64BITS: - assert Foo10.__fields_types__['c'].padding == 0 - assert Foo10.__fields_types__['p'].padding == 7 - assert Foo10.__fields_types__['s'].padding == 0 + assert Foo10.__fields_types__["c"].padding == 0 + assert Foo10.__fields_types__["p"].padding == 7 + assert Foo10.__fields_types__["s"].padding == 0 assert sizeof("struct Foo10") == 24 assert Foo10().size == 24 else: - assert Foo10.__fields_types__['c'].padding == 0 - assert Foo10.__fields_types__['p'].padding == 3 - assert Foo10.__fields_types__['s'].padding == 0 + assert Foo10.__fields_types__["c"].padding == 0 + assert Foo10.__fields_types__["p"].padding == 3 + assert Foo10.__fields_types__["s"].padding == 0 assert sizeof("struct Foo10") == 12 assert Foo10().size == 12 diff --git a/tests/test_c_expr.py b/tests/test_c_expr.py index 8230bd6..294fa24 100644 --- a/tests/test_c_expr.py +++ b/tests/test_c_expr.py @@ -38,7 +38,7 @@ def test_c_expr_def(): """ ) assert getdef("A1") == 10 - assert getdef('A2') == 20 # TODO + assert getdef("A2") == 20 # TODO assert c_eval("A1 / 10") == 1 @@ -76,5 +76,5 @@ def test_c_expr_compare(): assert c_eval("3 > 2 > 1") == 1 assert c_eval("3 >= 30") == 0 assert c_eval("3 <= 30") == 1 - define('A10', 10) + define("A10", 10) assert c_eval("((A10 < 6) || (A10>10))") == 0 diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index 57e9557..ae1d060 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -33,7 +33,7 @@ from pathlib import Path from cstruct.exceptions import ParserError -MBR_DATA = (Path(__file__).parent.parent / 'mbr').read_bytes() +MBR_DATA = (Path(__file__).parent.parent / "mbr").read_bytes() class Position(cstruct.CStruct): @@ -92,9 +92,9 @@ class Dummy(cstruct.CStruct): """ -typedef('char', 'BYTE') -typedef('short', 'WORD') -typedef('int', 'DWORD') +typedef("char", "BYTE") +typedef("short", "WORD") +typedef("int", "DWORD") class PartitionFlat(cstruct.CStruct): @@ -140,7 +140,7 @@ def test_len(): def test_pack_len(): - buffer = b'\x00' * 512 + buffer = b"\x00" * 512 mbr = MBR(buffer) d = mbr.pack() assert len(d) == 512 @@ -204,7 +204,7 @@ def test_clear(): def test_inline(): StructT1 = cstruct.parse( - 'struct StructT1 { unsigned char head; unsigned char sector; unsigned char cyl; }', + "struct StructT1 { unsigned char head; unsigned char sector; unsigned char cyl; }", __byte_order__=cstruct.LITTLE_ENDIAN, ) s = StructT1(head=254, sector=63, cyl=134) @@ -215,8 +215,8 @@ def test_inline(): def test_dummy(): dummy = Dummy() - dummy.c = b'A' - dummy.vc = b'ABCDEFGHIJ' + dummy.c = b"A" + dummy.vc = b"ABCDEFGHIJ" dummy.i = 123456 for i in range(0, 10): dummy.vi[i] = i * 10 @@ -261,9 +261,9 @@ def test_null_compare(): def test_invalid_inline(): with pytest.raises(ParserError): - cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) + cstruct.MemCStruct.parse("struct { unsigned char head; unsigned char head; }", __byte_order__=cstruct.LITTLE_ENDIAN) def test_invalid_inline_reserved(): with pytest.raises(ParserError): - cstruct.CStruct.parse('struct { int size; }') + cstruct.CStruct.parse("struct { int size; }") diff --git a/tests/test_define.py b/tests/test_define.py index eda2947..6a5a74e 100644 --- a/tests/test_define.py +++ b/tests/test_define.py @@ -46,32 +46,32 @@ class Position(cstruct.CStruct): def test_sizeof(): - assert sizeof('int') == 4 - define('INIT_THREAD_SIZE', 2048 * sizeof('long')) + assert sizeof("int") == 4 + define("INIT_THREAD_SIZE", 2048 * sizeof("long")) if IS_64BITS: - assert cstruct.DEFINES['INIT_THREAD_SIZE'] == 16384 + assert cstruct.DEFINES["INIT_THREAD_SIZE"] == 16384 else: - assert cstruct.DEFINES['INIT_THREAD_SIZE'] == 8192 - assert sizeof('struct Position') == 3 - assert sizeof('struct Position') == len(Position) + assert cstruct.DEFINES["INIT_THREAD_SIZE"] == 8192 + assert sizeof("struct Position") == 3 + assert sizeof("struct Position") == len(Position) assert sizeof(Position) == 3 with pytest.raises(KeyError): - sizeof('bla') + sizeof("bla") with pytest.raises(KeyError): - sizeof('struct Bla') + sizeof("struct Bla") def test_define(): - define('A', 10) - assert cstruct.DEFINES['A'] == 10 - undef('A') + define("A", 10) + assert cstruct.DEFINES["A"] == 10 + undef("A") with pytest.raises(KeyError): - cstruct.DEFINES['A'] + cstruct.DEFINES["A"] def test_typedef(): - typedef('int', 'integer') - assert sizeof('integer') == 4 + typedef("int", "integer") + assert sizeof("integer") == 4 def test_invalid_type(): diff --git a/tests/test_flexible_array.py b/tests/test_flexible_array.py index a8a33df..5a3aef6 100644 --- a/tests/test_flexible_array.py +++ b/tests/test_flexible_array.py @@ -53,26 +53,26 @@ class MemPkg(cstruct.MemCStruct): def test_len(): pkg = Pkg() - assert len(pkg) == sizeof('uint16_t') * 2 + assert len(pkg) == sizeof("uint16_t") * 2 assert len(pkg.pack()) - assert len(pkg) == sizeof('uint16_t') * 2 - assert pkg.sizeof() == sizeof('uint16_t') * 2 - assert pkg.__size__ == sizeof('uint16_t') * 2 + assert len(pkg) == sizeof("uint16_t") * 2 + assert pkg.sizeof() == sizeof("uint16_t") * 2 + assert pkg.__size__ == sizeof("uint16_t") * 2 pkg.length = 10 pkg.data = list(range(pkg.length)) - assert len(pkg.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert len(pkg) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert pkg.sizeof() == sizeof('uint16_t') * 2 - assert pkg.__size__ == sizeof('uint16_t') * 2 + assert len(pkg.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert len(pkg) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert pkg.sizeof() == sizeof("uint16_t") * 2 + assert pkg.__size__ == sizeof("uint16_t") * 2 pkg2 = Pkg() pkg2.length = 20 pkg2.data = list(range(pkg2.length)) - assert len(pkg2.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg2.length) - assert len(pkg2) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg2.length) - assert pkg2.sizeof() == sizeof('uint16_t') * 2 - assert pkg2.__size__ == sizeof('uint16_t') * 2 + assert len(pkg2.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg2.length) + assert len(pkg2) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg2.length) + assert pkg2.sizeof() == sizeof("uint16_t") * 2 + assert pkg2.__size__ == sizeof("uint16_t") * 2 assert len(pkg) != len(pkg2) @@ -80,20 +80,20 @@ def test_pack_unpack(): pkg = Pkg() pkg.cmd = 5 pkg.length = 10 - assert pkg.__fields_types__['data'].vlen == 0 - assert pkg.__fields_types__['data'].vsize == 0 - assert len(pkg) == sizeof('uint16_t') * 2 + assert pkg.__fields_types__["data"].vlen == 0 + assert pkg.__fields_types__["data"].vsize == 0 + assert len(pkg) == sizeof("uint16_t") * 2 pkg.data = list(range(pkg.length)) - assert len(pkg.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert pkg.__fields_types__['data'].vlen == pkg.length - assert pkg.__fields_types__['data'].vsize == (sizeof('uint8_t') * pkg.length) + assert len(pkg.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert pkg.__fields_types__["data"].vlen == pkg.length + assert pkg.__fields_types__["data"].vsize == (sizeof("uint8_t") * pkg.length) assert len(pkg.data) == pkg.length data = pkg.pack() pkg2 = Pkg() - assert pkg2.__fields_types__['data'].vlen == 0 + assert pkg2.__fields_types__["data"].vlen == 0 pkg2.unpack(data, flexible_array_length=pkg.length) - assert pkg2.__fields_types__['data'].vlen == pkg2.length + assert pkg2.__fields_types__["data"].vlen == pkg2.length assert pkg2.cmd == pkg.cmd assert pkg2.length == pkg.length assert pkg2.data == pkg.data @@ -108,33 +108,33 @@ def test_pack_unpack(): def test_mem_len(): pkg = MemPkg() - assert len(pkg) == sizeof('uint16_t') * 2 + assert len(pkg) == sizeof("uint16_t") * 2 assert len(pkg.pack()) - assert len(pkg) == sizeof('uint16_t') * 2 - assert pkg.sizeof() == sizeof('uint16_t') * 2 - assert pkg.__size__ == sizeof('uint16_t') * 2 + assert len(pkg) == sizeof("uint16_t") * 2 + assert pkg.sizeof() == sizeof("uint16_t") * 2 + assert pkg.__size__ == sizeof("uint16_t") * 2 pkg.length = 10 pkg.data = list(range(pkg.length)) - assert len(pkg.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert len(pkg) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert pkg.sizeof() == sizeof('uint16_t') * 2 - assert pkg.__size__ == sizeof('uint16_t') * 2 + assert len(pkg.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert len(pkg) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert pkg.sizeof() == sizeof("uint16_t") * 2 + assert pkg.__size__ == sizeof("uint16_t") * 2 pkg.length = 5 pkg.data = list(range(pkg.length)) - assert len(pkg.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert len(pkg) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert pkg.sizeof() == sizeof('uint16_t') * 2 - assert pkg.__size__ == sizeof('uint16_t') * 2 + assert len(pkg.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert len(pkg) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert pkg.sizeof() == sizeof("uint16_t") * 2 + assert pkg.__size__ == sizeof("uint16_t") * 2 pkg2 = MemPkg() pkg2.length = 20 pkg2.data = list(range(pkg2.length)) - assert len(pkg2.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg2.length) - assert len(pkg2) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg2.length) - assert pkg2.sizeof() == sizeof('uint16_t') * 2 - assert pkg2.__size__ == sizeof('uint16_t') * 2 + assert len(pkg2.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg2.length) + assert len(pkg2) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg2.length) + assert pkg2.sizeof() == sizeof("uint16_t") * 2 + assert pkg2.__size__ == sizeof("uint16_t") * 2 assert len(pkg) != len(pkg2) @@ -142,20 +142,20 @@ def test_mem_pack_unpack(): pkg = MemPkg() pkg.cmd = 5 pkg.length = 10 - assert pkg.__fields_types__['data'].vlen == 0 - assert pkg.__fields_types__['data'].vsize == 0 - assert len(pkg) == sizeof('uint16_t') * 2 + assert pkg.__fields_types__["data"].vlen == 0 + assert pkg.__fields_types__["data"].vsize == 0 + assert len(pkg) == sizeof("uint16_t") * 2 pkg.data = list(range(pkg.length)) - assert len(pkg.pack()) == (sizeof('uint16_t') * 2) + (sizeof('uint8_t') * pkg.length) - assert pkg.__fields_types__['data'].vlen == pkg.length - assert pkg.__fields_types__['data'].vsize == (sizeof('uint8_t') * pkg.length) + assert len(pkg.pack()) == (sizeof("uint16_t") * 2) + (sizeof("uint8_t") * pkg.length) + assert pkg.__fields_types__["data"].vlen == pkg.length + assert pkg.__fields_types__["data"].vsize == (sizeof("uint8_t") * pkg.length) assert len(pkg.data) == pkg.length data = pkg.pack() pkg2 = MemPkg() - assert pkg2.__fields_types__['data'].vlen == 0 + assert pkg2.__fields_types__["data"].vlen == 0 pkg2.unpack(data, flexible_array_length=pkg.length) - assert pkg2.__fields_types__['data'].vlen == pkg2.length + assert pkg2.__fields_types__["data"].vlen == pkg2.length assert pkg2.cmd == pkg.cmd assert pkg2.length == pkg.length assert pkg2.data == pkg.data diff --git a/tests/test_memcstruct.py b/tests/test_memcstruct.py index e5c1dca..bac47c1 100644 --- a/tests/test_memcstruct.py +++ b/tests/test_memcstruct.py @@ -32,7 +32,7 @@ from pathlib import Path from cstruct.exceptions import ParserError -MBR_DATA = (Path(__file__).parent.parent / 'mbr').read_bytes() +MBR_DATA = (Path(__file__).parent.parent / "mbr").read_bytes() class Position(cstruct.MemCStruct): @@ -89,9 +89,9 @@ class Dummy(cstruct.MemCStruct): """ -typedef('char', 'BYTE') -typedef('short', 'WORD') -typedef('int', 'DWORD') +typedef("char", "BYTE") +typedef("short", "WORD") +typedef("int", "DWORD") class PartitionFlat(cstruct.MemCStruct): @@ -137,7 +137,7 @@ def test_len(): def test_pack_len(): - buffer = b'\x00' * 512 + buffer = b"\x00" * 512 mbr = MBR(buffer) d = mbr.pack() assert len(d) == 512 @@ -200,7 +200,7 @@ def test_clear(): def test_inline(): TestStruct = cstruct.MemCStruct.parse( - 'struct { unsigned char head; unsigned char sector; unsigned char cyl; }', __byte_order__=cstruct.LITTLE_ENDIAN + "struct { unsigned char head; unsigned char sector; unsigned char cyl; }", __byte_order__=cstruct.LITTLE_ENDIAN ) s = TestStruct(head=254, sector=63, cyl=134) p = Position(head=254, sector=63, cyl=134) @@ -209,8 +209,8 @@ def test_inline(): def test_dummy(): dummy = Dummy() - dummy.c = b'A' - dummy.vc = b'ABCDEFGHIJ' + dummy.c = b"A" + dummy.vc = b"ABCDEFGHIJ" dummy.i = 123456 for i in range(0, 10): dummy.vi[i] = i * 10 @@ -255,9 +255,9 @@ def test_null_compare(): def test_invalid_inline(): with pytest.raises(ParserError): - cstruct.MemCStruct.parse('struct { unsigned char head; unsigned char head; }', __byte_order__=cstruct.LITTLE_ENDIAN) + cstruct.MemCStruct.parse("struct { unsigned char head; unsigned char head; }", __byte_order__=cstruct.LITTLE_ENDIAN) def test_invalid_inline_reserved(): with pytest.raises(ParserError): - cstruct.MemCStruct.parse('struct { int size; }') + cstruct.MemCStruct.parse("struct { int size; }") diff --git a/tests/test_nested.py b/tests/test_nested.py index 4220ca5..b6b0bbf 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -113,7 +113,7 @@ def test_invalid_anonymous(): def test_sizeof_nested_struct(): - assert sizeof('struct NestedStruct') == 16 + assert sizeof("struct NestedStruct") == 16 o = NestedStruct() assert len(o) == 16 @@ -135,7 +135,7 @@ def test_pack_unpack_nested_struct(): def test_sizeof_nested_union(): - assert sizeof('struct NestedUnion') == 8 + assert sizeof("struct NestedUnion") == 8 o = NestedUnion() assert len(o) == 8 @@ -165,7 +165,7 @@ def test_pack_unpack_nested_union(): def test_sizeof_nested_anonymous_union(): - assert sizeof('struct NestedAnonymousUnion') == 8 + assert sizeof("struct NestedAnonymousUnion") == 8 o = NestedAnonymousUnion() assert len(o) == 8 diff --git a/tests/test_typdef.py b/tests/test_typdef.py index 9990f8b..ca8a564 100644 --- a/tests/test_typdef.py +++ b/tests/test_typdef.py @@ -70,7 +70,7 @@ class Utmp(MemCStruct): def test_typedef(): - assert TYPEDEFS['pid_t'] == 'int' - assert TYPEDEFS['time_t'] == 'long' - assert TYPEDEFS['ulong'] == 'unsigned long' - assert TYPEDEFS['ExitStatus'] == 'struct ExitStatus' + assert TYPEDEFS["pid_t"] == "int" + assert TYPEDEFS["time_t"] == "long" + assert TYPEDEFS["ulong"] == "unsigned long" + assert TYPEDEFS["ExitStatus"] == "struct ExitStatus" diff --git a/tests/test_union.py b/tests/test_union.py index ad90713..a13e97f 100644 --- a/tests/test_union.py +++ b/tests/test_union.py @@ -70,7 +70,7 @@ class UnionT1(cstruct.MemCStruct): def test_sizeof(): - assert sizeof('struct UnionT1') == 64 + assert sizeof("struct UnionT1") == 64 s = UnionT1() assert len(s) == 64 @@ -82,12 +82,12 @@ def test_union_unpack(): assert union.a1 == 0 assert union.b == 0 assert union.c == 0 - union.unpack(struct.pack('b', 10) + cstruct.CHAR_ZERO * union.size) + union.unpack(struct.pack("b", 10) + cstruct.CHAR_ZERO * union.size) assert union.a == 10 assert union.a1 == 10 assert union.b == 10 assert union.c == 10 - union.unpack(struct.pack('h', 1979) + cstruct.CHAR_ZERO * union.size) + union.unpack(struct.pack("h", 1979) + cstruct.CHAR_ZERO * union.size) assert union.a == 187 assert union.a1 == 187 assert union.b == 1979 From 3a0ea6efe513171b197713d78591e227e82eca30 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 12 Nov 2022 07:58:19 +0000 Subject: [PATCH 48/78] setup --- .github/workflows/release.yml | 2 +- pyproject.toml | 4 +++ requirements-dev.txt | 1 + setup.cfg | 61 +++++++++++++++++++++++++++++++++-- setup.py | 46 +++----------------------- 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75addee..c1e0e3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: python -m pip install -r requirements-dev.txt - name: Build package - run: python setup.py bdist_wheel sdist + run: python -m build - name: Check package run: twine check dist/* diff --git a/pyproject.toml b/pyproject.toml index 70b86b1..074200f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + [tool.black] line-length = 132 diff --git a/requirements-dev.txt b/requirements-dev.txt index ff893de..7584936 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,3 +10,4 @@ mkdocs mkdocstrings[python] mkdocs-material markdown_include +build diff --git a/setup.cfg b/setup.cfg index c5ee45d..2b497a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,61 @@ +[metadata] +name = cstruct +version = attr: cstruct.__version__ +keywords = struct, cstruct, enum, binary, pack, unpack +description = C-style structs for Python +author = Andrea Bonomi +author_email = andrea.bonomi@gmail.com +url = http://github.com/andreax79/python-cstruct +long_description = file: README.md +long_description_content_type = text/markdown +license = MIT +license_files = LICENSE +platforms = any +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Console + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python + Topic :: Software Development :: Libraries :: Python Modules + Programming Language :: Python :: 3.6 + 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 +project_urls = + Bug Tracker = http://github.com/andreax79/python-cstruct/issues + Documentation = https://python-cstruct.readthedocs.io/en/latest/ + Source Code = http://github.com/andreax79/python-cstruct + +[options] +zip_safe = True +include_package_data = True +python_requires = >=3.6 +packages = find: + +[options.packages.find] +include = cstruct* +exclude = + ez_setup + examples + tests + +[options.extras_require] +test = pytest + +[aliases] +test = pytest + [bdist_wheel] -universal=1 +universal = 1 [flake8] -max-line-length=132 -ignore: E401, W504, E221 +max-line-length = 132 +extend-ignore = + E203 + E401 + W504 + E221 diff --git a/setup.py b/setup.py index bfeaed7..dbe9716 100644 --- a/setup.py +++ b/setup.py @@ -1,43 +1,5 @@ -from setuptools import setup, find_packages -from cstruct import __version__ +#!/usr/bin/env python +import setuptools - -def readme(): - with open('README.md') as f: - return f.read() - - -setup( - name='cstruct', - version=__version__, - description="C-style structs for Python", - long_description="""\ -Convert C struct definitions into Python classes with methods for serializing/deserializing.""", - classifiers=[ - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 3.6', - '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', - ], - keywords='struct', - author='Andrea Bonomi', - author_email='andrea.bonomi@gmail.com', - url='http://github.com/andreax79/python-cstruct', - license='MIT', - packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), - include_package_data=True, - zip_safe=True, - install_requires=[ - # -*- Extra requirements: -*- - ], - entry_points=""" - # -*- Entry points: -*- - """, - test_suite='tests', - tests_require=['pytest'], -) +if __name__ == "__main__": + setuptools.setup() From 14f0e93634728a1bb625da2f8bd58519d57e4362 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 12 Nov 2022 08:02:29 +0000 Subject: [PATCH 49/78] version 5.0 --- changelog.txt | 15 +++++++++++++++ cstruct/__init__.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 5a46494..a7a35c2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -175,3 +175,18 @@ ### Improved - Python 3.11 support + +## 5.0 + +2022-11-12 + +### Added + +- Add support for enums +- Add support for multiple definition to cstruct.parse +- Add inspect method + +### Improved + +- Documentation and examples +- Restructure setup diff --git a/cstruct/__init__.py b/cstruct/__init__.py index a20e843..13af539 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "4.0" +__version__ = "5.0" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union From 7149384cd04ab6c9c087384b3ccdc4bf356fcf9a Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 20 Nov 2022 07:26:05 +0000 Subject: [PATCH 50/78] add support for char constants --- README.md | 5 +++++ cstruct/c_expr.py | 7 +++++++ tests/test_c_expr.py | 5 +++++ tests/test_cenum.py | 6 ++++++ 4 files changed, 23 insertions(+) diff --git a/README.md b/README.md index cb6dee4..71e547b 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ Different enum styles are supported in struct/union definitions. enum Type_A a; // externally defined using CEnum enum Type_B {A, B, C} b; enum {A, B, C} c; +enum Type_D : short {A, B, C} d; // specify the underlying type +enum Direction { left = 'l', right = 'r' }; ``` ### Nested structs/unions @@ -334,3 +336,6 @@ Output example: 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| ``` +Links +----- +* [C/C++ reference](https://en.cppreference.com/) diff --git a/cstruct/c_expr.py b/cstruct/c_expr.py index d46406f..58fc1e3 100644 --- a/cstruct/c_expr.py +++ b/cstruct/c_expr.py @@ -58,6 +58,8 @@ def c_eval(expr: str) -> Union[int, float]: try: expr = expr.replace("!", " not ").replace("&&", " and ").replace("||", " or ") return eval_node(ast.parse(expr.strip()).body[0]) + except EvalError: + raise except Exception: raise EvalError @@ -67,6 +69,11 @@ def eval_node(node: ast.stmt) -> Union[int, float]: result = handler(node) if isinstance(result, bool): # convert bool to int return 1 if result else 0 + elif isinstance(result, str): # convert char to int + if len(result) != 1: + raise EvalError("Multi-character constant") + else: + return ord(result) return result diff --git a/tests/test_c_expr.py b/tests/test_c_expr.py index 294fa24..da8c89b 100644 --- a/tests/test_c_expr.py +++ b/tests/test_c_expr.py @@ -78,3 +78,8 @@ def test_c_expr_compare(): assert c_eval("3 <= 30") == 1 define("A10", 10) assert c_eval("((A10 < 6) || (A10>10))") == 0 + + +def test_c_expr_char(): + assert c_eval("'A'") == 65 + assert c_eval("'B'") == 66 diff --git a/tests/test_cenum.py b/tests/test_cenum.py index 5823a12..bea6aa2 100644 --- a/tests/test_cenum.py +++ b/tests/test_cenum.py @@ -104,3 +104,9 @@ def test_type(): color = cstruct.parse("enum Color : unsigned short { red, green, blue };") assert color.__size__ == 2 assert cstruct.sizeof("enum Color") == 2 + + +def test_char(): + direction = cstruct.parse("enum Direction { left = 'l', right = 'r' };") + assert direction.__size__ == 4 + assert cstruct.sizeof("enum Direction") == 4 From 45a5ea480da47f2e5e190ed11b28d83a841cd3cc Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 20 Nov 2022 16:20:02 +0000 Subject: [PATCH 51/78] native type test --- tests/test_native_types.py | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/test_native_types.py diff --git a/tests/test_native_types.py b/tests/test_native_types.py new file mode 100644 index 0000000..5a46710 --- /dev/null +++ b/tests/test_native_types.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# ***************************************************************************** +# +# Copyright (c) 2013-2019 Andrea Bonomi +# +# Published under the terms of the MIT license. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# ***************************************************************************** + +from cstruct import MemCStruct, sizeof +from cstruct.base import TYPEDEFS +from cstruct.native_types import AbstractNativeType + + +class SizeT(AbstractNativeType): + type_name = "size_t" + native_format = "Q" + + +class StructWithSizeT(MemCStruct): + __def__ = """ + typedef size_t size; + typedef unsigned long long ull; + + struct StructWithSizeT { + ull a; + size_t b; + size c; + } + """ + + +def test_typedef(): + assert TYPEDEFS["size"] == "size_t" + assert TYPEDEFS["ull"] == "unsigned long long" + + +def test_sizeof_custom_native_type(): + assert sizeof("size_t") == 8 + assert sizeof("ull") == 8 + assert sizeof("struct StructWithSizeT") == 8 * 3 From 66041fa76d30847a89c8e5c6d0f6f55c356696bf Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 20 Nov 2022 16:37:59 +0000 Subject: [PATCH 52/78] example of integration with libc via ctypes --- README.md | 1 + docs/examples/dir.md | 6 +++ examples/dir.c | 22 +++++++++++ examples/dir.py | 94 ++++++++++++++++++++++++++++++++++++++++++++ examples/dir.sh | 3 ++ mkdocs.yml | 1 + 6 files changed, 127 insertions(+) create mode 100644 docs/examples/dir.md create mode 100644 examples/dir.c create mode 100644 examples/dir.py create mode 100755 examples/dir.sh diff --git a/README.md b/README.md index 71e547b..161c36e 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Examples * [Read the DOS-type (MBR) partition table](https://python-cstruct.readthedocs.io/en/latest/examples/fdisk/) * [Print information about logged uses](https://python-cstruct.readthedocs.io/en/latest/examples/who/) * [Flexible Array Member (FAM)](https://python-cstruct.readthedocs.io/en/latest/examples/flexible_array/) +* [libc integration (using ctypes)](https://python-cstruct.readthedocs.io/en/latest/examples/dir/) Features diff --git a/docs/examples/dir.md b/docs/examples/dir.md new file mode 100644 index 0000000..3710ad1 --- /dev/null +++ b/docs/examples/dir.md @@ -0,0 +1,6 @@ +The following program prints the names of the files in a directory, +calling the libc functions `getcwd`, `opendir`, `readdir`, and `closedir`: + +``` +{!examples/dir.py!} +``` diff --git a/examples/dir.c b/examples/dir.c new file mode 100644 index 0000000..912a7f7 --- /dev/null +++ b/examples/dir.c @@ -0,0 +1,22 @@ +/* https://www.gnu.org/software/libc/manual/html_mono/libc.html#Simple-Directory-Lister */ + +#include +#include +#include + +int main (void) { + DIR *dp; + struct dirent *ep; + + dp = opendir("."); + if (dp != NULL) { + while (ep = readdir (dp)) { + puts(ep->d_name); + } + closedir(dp); + } else { + perror("Couldn't open the directory"); + } + + return 0; +} diff --git a/examples/dir.py b/examples/dir.py new file mode 100644 index 0000000..0cde462 --- /dev/null +++ b/examples/dir.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +import cstruct +import ctypes +import sys + +libc = ctypes.cdll.LoadLibrary("libc.so.6") +# opendir +libc.opendir.argtypes = [ctypes.c_char_p] +libc.opendir.restype = ctypes.c_void_p +# readdir +libc.readdir.argtypes = [ctypes.c_void_p] +libc.readdir.restype = ctypes.c_void_p +# closedir +libc.closedir.argtypes = [ctypes.c_void_p] +libc.closedir.restype = ctypes.c_int + + +class DType(cstruct.CEnum): + __size__ = 1 + __def__ = """ + enum d_type { + DT_UNKNOWN = 0x0, + DT_FIFO = 0x1, + DT_CHR = 0x2, + DT_DIR = 0x4, + DT_BLK = 0x6, + DT_REG = 0x8, + DT_LNK = 0xa, + DT_SOCK = 0xc + }; + """ + + def __str__(self): + return { + DType.DT_UNKNOWN: "", + DType.DT_FIFO: "", + DType.DT_CHR: "", + DType.DT_DIR: "", + DType.DT_BLK: "", + DType.DT_REG: "", + DType.DT_LNK: "", + DType.DT_SOCK: "", + }[self] + + +class Dirent(cstruct.MemCStruct): + __def__ = """ + #define PATH_MAX 4096 + + typedef long ino_t; + typedef long off_t; + + struct dirent { + ino_t d_ino; /* Inode number */ + off_t d_off; /* Not an offset */ + unsigned short d_reclen; /* Length of this record */ + unsigned char d_type; /* Type of file; not supported + by all filesystem types */ + char d_name[256]; /* Null-terminated filename */ + }; + """ + + @property + def name(self): + return ctypes.c_char_p(self.d_name).value.decode("ascii") + + @property + def type(self): + return DType(self.d_type) + + +def main(): + if len(sys.argv) > 1: + cwd = ctypes.create_string_buffer(sys.argv[1].encode("ascii")) + else: + # Get current dir + cwd = ctypes.create_string_buffer(cstruct.getdef("PATH_MAX") + 1) + assert libc.getcwd(cwd, ctypes.sizeof(cwd)) != 0 + # Open dir + dp = libc.opendir(cwd) + assert dp != 0 + # Read dir entries + ep = libc.readdir(dp) + while ep: + contents = ctypes.cast(ep, ctypes.POINTER(ctypes.c_char * Dirent.size)).contents + dirent = Dirent(contents) + print(f"{dirent.d_ino:8} {dirent.type:10} {dirent.name}") + ep = libc.readdir(dp) + # Close dir + libc.closedir(dp) + + +if __name__ == "__main__": + main() diff --git a/examples/dir.sh b/examples/dir.sh new file mode 100755 index 0000000..8fc463c --- /dev/null +++ b/examples/dir.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cd "$(dirname "$0")/.." || exit +python -m examples.dir $* diff --git a/mkdocs.yml b/mkdocs.yml index 435ebbe..29c11cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,6 +20,7 @@ nav: - Code of Conduct: CODE_OF_CONDUCT.md - Source Code Repository: "https://github.com/andreax79/python-cstruct" - Examples: + - "dir.py": examples/dir.md - "fdisk.py": examples/fdisk.md - "flexible_array.py": examples/flexible_array.md - "who.py": examples/who.md From 84be7faee3a8edd7295874f5d6821ca979586845 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 20 Nov 2022 16:54:06 +0000 Subject: [PATCH 53/78] support unpack from ctype pointers --- cstruct/mem_cstruct.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cstruct/mem_cstruct.py b/cstruct/mem_cstruct.py index dc2f9c3..e078f62 100644 --- a/cstruct/mem_cstruct.py +++ b/cstruct/mem_cstruct.py @@ -73,6 +73,9 @@ def unpack_from(self, buffer: Optional[bytes], offset: int = 0, flexible_array_l self.__mem__ = ctypes.create_string_buffer(self.size + 1) elif isinstance(buffer, ctypes.Array): self.__mem__ = buffer + elif isinstance(buffer, int): + # buffer is a pointer + self.__mem__ = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_char * self.size)).contents else: self.__mem__ = ctypes.create_string_buffer(buffer) for field, field_type in self.__fields_types__.items(): From 1dafc6b1c03a952133f409ae65c1bb94757f2469 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 20 Nov 2022 20:33:52 +0000 Subject: [PATCH 54/78] add support for char constants in python < 3.8 --- cstruct/c_expr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cstruct/c_expr.py b/cstruct/c_expr.py index 58fc1e3..b7d0796 100644 --- a/cstruct/c_expr.py +++ b/cstruct/c_expr.py @@ -126,6 +126,7 @@ def eval_call(node) -> Union[int, float]: ast.Name: eval_get, ast.Call: eval_call, Constant: lambda node: node.value, + ast.Str: lambda node: node.s, # python < 3.8 # and/or ast.BoolOp: lambda node: OPS[type(node.op)](node), # and/or operator ast.And: lambda node: all(eval_node(x) for x in node.values), # && operator From cc86c656445b11a3d2e46a596c7e99e856e2dd39 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sun, 20 Nov 2022 20:37:55 +0000 Subject: [PATCH 55/78] version 5.1 --- changelog.txt | 14 ++++++++++++++ cstruct/__init__.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index a7a35c2..d9b4924 100644 --- a/changelog.txt +++ b/changelog.txt @@ -190,3 +190,17 @@ - Documentation and examples - Restructure setup + +## 5.1 + +2022-11-20 + +### Improved + +- Support unpack from ctype pointers + +### Added + +- Add support for char constants +- Add native type test +- dir.py example diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 13af539..cbea067 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "5.0" +__version__ = "5.1" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union From bd3ff53e2175c5c134da4bd6745ed6d36d0fdcc4 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 23 Nov 2022 09:50:34 +0000 Subject: [PATCH 56/78] fix nested struct unpack --- cstruct/mem_cstruct.py | 3 ++- tests/test_nested.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/cstruct/mem_cstruct.py b/cstruct/mem_cstruct.py index e078f62..fcbf41f 100644 --- a/cstruct/mem_cstruct.py +++ b/cstruct/mem_cstruct.py @@ -56,6 +56,7 @@ class MemCStruct(AbstractCStruct): """ __mem__ = None + __base__ = 0 def unpack_from(self, buffer: Optional[bytes], offset: int = 0, flexible_array_length: Optional[int] = None) -> bool: """ @@ -101,7 +102,7 @@ def pack(self) -> bytes: Returns: bytes: The packed structure """ - return self.__mem__.raw[:-1] # the buffer is one item larger than its size and the last element is NUL + return self.__mem__.raw[self.__base__ : self.__base__ + self.size] def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> None: """ diff --git a/tests/test_nested.py b/tests/test_nested.py index b6b0bbf..6acf9d8 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -210,3 +210,52 @@ def test_nested_anonymous_union_struct(): assert o1.format1.field1 == 11 assert o1.format1.field2 == 12 assert o1.format1.field3 == 13 + + +def test_nested_struct_offset(): + cstruct.parse( + """ + struct op_a { + int a; + }; + + struct op_b { + char a; + char b; + char c; + }; + """, + __byte_order__=cstruct.LITTLE_ENDIAN, + ) + + Op = cstruct.parse( + """ + struct op { + char preamble[10]; + uint64_t magic; + union { + struct op_a a_op; + struct op_b b_op; + } u1; + struct op_a aaa; + }; + """, + __byte_order__=cstruct.LITTLE_ENDIAN, + ) + + o = Op() + o.preamble = b'ciao_ciao' + o.magic = 3771778641802345472 + o.u1.a_op.a = 2022 + o.aaa.a = 0x33333333 + assert o.u1.b_op.a == b'\xe6' + assert o.u1.b_op.b == b'\x07' + assert o.u1.b_op.c == b'\x00' + assert o.__base__ == 0 + assert o.u1.__base__ >= 10 + assert o.u1.__base__ == o.u1.a_op.__base__ + assert o.u1.__base__ == o.u1.b_op.__base__ + assert o.aaa.__base__ > o.u1.__base__ + assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' + assert o.u1.pack() == b'\xe6\x07\x00\x00' + assert o.aaa.pack() == b'3333' From 364a13f0742f7c762ea2f7502241bb10aff68927 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 23 Nov 2022 11:02:20 +0000 Subject: [PATCH 57/78] fix nested anonymous union offset --- cstruct/c_parser.py | 4 +++ tests/test_nested.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 16cedb5..a6865bf 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -426,6 +426,10 @@ def parse_struct( for nested_field_name, nested_field_type in field_type.ref.__fields_types__.items(): if nested_field_name in fields_types: raise ParserError(f"Duplicate member `{nested_field_name}`") + # set the corret offset + nested_field_type = nested_field_type.copy() + nested_field_type.base_offset = offset + nested_field_type.base_offset + nested_field_type.offset = offset + nested_field_type.offset fields_types[nested_field_name] = nested_field_type vname = f"__anonymous{anonymous}" anonymous += 1 diff --git a/tests/test_nested.py b/tests/test_nested.py index 6acf9d8..3b64a71 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -259,3 +259,69 @@ def test_nested_struct_offset(): assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' assert o.u1.pack() == b'\xe6\x07\x00\x00' assert o.aaa.pack() == b'3333' + + +def test_nested_anonymous_struct_offset(): + cstruct.parse( + """ + struct op_a1 { + int a; + }; + + struct op_b1 { + char a; + char b; + char c; + }; + """, + __byte_order__=cstruct.LITTLE_ENDIAN, + ) + + Opu = cstruct.parse( + """ + struct opu { + char preamble[10]; + uint64_t magic; + union { + struct op_a1 a_op; + struct op_b1 b_op; + }; + struct op_a aaa; + }; + """, + __byte_order__=cstruct.LITTLE_ENDIAN, + ) + + o = Opu() + o.preamble = b'ciao_ciao' + o.magic = 3771778641802345472 + o.__anonymous0.a_op.a = 2022 + o.aaa.a = 0x33333333 + assert o.__anonymous0.b_op.a == b'\xe6' + assert o.__anonymous0.b_op.b == b'\x07' + assert o.__anonymous0.b_op.c == b'\x00' + assert o.__base__ == 0 + assert o.__anonymous0.__base__ >= 10 + assert o.__anonymous0.__base__ == o.__anonymous0.a_op.__base__ + assert o.__anonymous0.__base__ == o.__anonymous0.b_op.__base__ + assert o.aaa.__base__ > o.__anonymous0.__base__ + assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' + assert o.__anonymous0.pack() == b'\xe6\x07\x00\x00' + assert o.aaa.pack() == b'3333' + + o = Opu() + o.preamble = b'ciao_ciao' + o.magic = 3771778641802345472 + o.a_op.a = 2022 + o.aaa.a = 0x33333333 + assert o.b_op.a == b'\xe6' + assert o.b_op.b == b'\x07' + assert o.b_op.c == b'\x00' + assert o.__base__ == 0 + assert o.__anonymous0.__base__ >= 10 + assert o.__anonymous0.__base__ == o.a_op.__base__ + assert o.__anonymous0.__base__ == o.b_op.__base__ + assert o.aaa.__base__ > o.__base__ + assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' + assert o.a_op.pack() == b'\xe6\x07\x00\x00' + assert o.aaa.pack() == b'3333' From e3c44497eef61978709ae9ada537125ab92a5014 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 23 Nov 2022 11:07:07 +0000 Subject: [PATCH 58/78] fix inspect offset for nested struct/union --- cstruct/abstract.py | 13 ++++++++++--- tests/test_nested.py | 7 +++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cstruct/abstract.py b/cstruct/abstract.py index d97bca5..b1f6e35 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -238,18 +238,25 @@ def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = No """ buffer = StringIO() if hasattr(self, "__mem__"): - mem = self.__mem__ + mem = self.__mem__[self.__base__ :] else: mem = self.pack() - for i in range(start_addr or 0, end_addr or self.size, 16): - row = mem[i : i + 16] + if end_addr is None: + end_addr = self.size + for i in range(start_addr or 0, end_addr, 16): + row = mem[i : min(i + 16, end_addr)] buffer.write(f"{i:08x} ") for j, c in enumerate(row): separator = " " if j == 7 else "" buffer.write(f" {c:02x}{separator}") + for j in range(len(row) - 1, 15): + separator = " " if j == 7 else "" + buffer.write(f" {separator}") buffer.write(" |") for c in row: buffer.write(chr(c) if c >= 32 and c < 127 else ".") + for j in range(len(row) - 1, 15): + buffer.write(" ") buffer.write("|") buffer.write("\n") buffer.seek(0, 0) diff --git a/tests/test_nested.py b/tests/test_nested.py index 3b64a71..c5a877b 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -259,6 +259,8 @@ def test_nested_struct_offset(): assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' assert o.u1.pack() == b'\xe6\x07\x00\x00' assert o.aaa.pack() == b'3333' + assert o.u1.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" + assert o.u1.b_op.inspect() == "00000000 e6 07 00 |... |\n" def test_nested_anonymous_struct_offset(): @@ -308,6 +310,9 @@ def test_nested_anonymous_struct_offset(): assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' assert o.__anonymous0.pack() == b'\xe6\x07\x00\x00' assert o.aaa.pack() == b'3333' + assert o.__anonymous0.inspect() == "00000000 e6 07 00 00 |.... |\n" + assert o.__anonymous0.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" + assert o.__anonymous0.b_op.inspect() == "00000000 e6 07 00 |... |\n" o = Opu() o.preamble = b'ciao_ciao' @@ -325,3 +330,5 @@ def test_nested_anonymous_struct_offset(): assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' assert o.a_op.pack() == b'\xe6\x07\x00\x00' assert o.aaa.pack() == b'3333' + assert o.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" + assert o.b_op.inspect() == "00000000 e6 07 00 |... |\n" From d1223b542ae4d890659b11a09d8d07d3028c7786 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 23 Nov 2022 11:08:50 +0000 Subject: [PATCH 59/78] version 5.2 --- changelog.txt | 10 ++++++++++ cstruct/__init__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index d9b4924..6aece75 100644 --- a/changelog.txt +++ b/changelog.txt @@ -204,3 +204,13 @@ - Add support for char constants - Add native type test - dir.py example + +## 5.2 + +2022-11-23 + +### Fix + +- nested struct unpack fix +- nested anonymous union offset fix +- inspect offset for nested struct/union fix diff --git a/cstruct/__init__.py b/cstruct/__init__.py index cbea067..b549276 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "5.1" +__version__ = "5.2" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union From 65e266e4c41d69af309fe54f6d340b8280ae3102 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 14 Dec 2022 10:26:35 +0100 Subject: [PATCH 60/78] Upgrade setuptools to 65.5.1 --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7584936..eaeedf2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,3 +11,4 @@ mkdocstrings[python] mkdocs-material markdown_include build +setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability From 1d818a39f759c512e8e6a83ebe0255e2a58463d3 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 14 Dec 2022 10:36:06 +0100 Subject: [PATCH 61/78] Update test version matrix --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 45e5185..28f196a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,11 +11,11 @@ jobs: strategy: matrix: python: - - "3.6" - "3.7" - "3.8" - "3.9" - "3.10" + - "3.11" runs-on: ubuntu-latest From 854b2e32bdd8d98239fc06dd6b951260a944bb9a Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Thu, 28 Dec 2023 13:39:01 +0100 Subject: [PATCH 62/78] Update github workflows --- .github/workflows/release.yml | 8 ++++---- .github/workflows/tests.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1e0e3b..6b8f5e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,19 +31,19 @@ jobs: strategy: matrix: - python-version: ['3.9'] + python-version: ['3.12'] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/requirements*.txt') }}-git-${{ github.sha }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 28f196a..41d40e3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,25 +11,25 @@ jobs: strategy: matrix: python: - - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" + - "3.12" runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/requirements*.txt') }}-git-${{ github.sha }} From c1ac3047f9f26ab16baf9eb761b4566f20d03778 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Thu, 28 Dec 2023 13:40:43 +0100 Subject: [PATCH 63/78] Python 3.12 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 2b497a6..59d4b3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 project_urls = Bug Tracker = http://github.com/andreax79/python-cstruct/issues Documentation = https://python-cstruct.readthedocs.io/en/latest/ From 1b980819c00c58b23b0d29c4ccab070cbddfba30 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Thu, 28 Dec 2023 13:47:25 +0100 Subject: [PATCH 64/78] isort --- Makefile | 10 ++++++++-- README.md | 1 + cstruct/__init__.py | 15 ++++++++------- cstruct/abstract.py | 17 +++++++++-------- cstruct/base.py | 4 ++-- cstruct/c_expr.py | 3 ++- cstruct/c_parser.py | 9 +++++---- cstruct/cstruct.py | 3 ++- cstruct/field.py | 5 +++-- cstruct/mem_cstruct.py | 3 ++- cstruct/native_types.py | 2 +- examples/dir.py | 3 ++- examples/fdisk.py | 5 +++-- examples/flexible_array.py | 3 ++- examples/who.py | 5 +++-- pyproject.toml | 3 +++ requirements-dev.txt | 19 ++++++++++--------- tests/test_alignment.py | 3 ++- tests/test_c_expr.py | 2 +- tests/test_cenum.py | 4 +++- tests/test_cstruct.py | 8 +++++--- tests/test_define.py | 4 +++- tests/test_get_type.py | 1 + tests/test_memcstruct.py | 6 ++++-- tests/test_nested.py | 2 +- tests/test_pickle.py | 1 + tests/test_typdef.py | 2 +- tests/test_union.py | 3 ++- 28 files changed, 90 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index 6b9e616..8986a88 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ SHELL=/bin/bash -e help: @echo - make black ------ Format code + @echo - make isort ------ Sort imports @echo - make clean ------ Clean virtual environment @echo - make coverage --- Run tests coverage @echo - make docs ------- Make docs @@ -11,8 +12,13 @@ help: @echo - make typecheck -- Typecheck @echo - make venv ------- Create virtual environment -black: - black -S cstruct tests examples setup.py +.PHONY: isort +isort: + @isort --profile black cstruct tests examples setup.py + +.PHONY: black +black: isort + @black -S cstruct tests examples setup.py clean: -rm -rf build dist diff --git a/README.md b/README.md index 161c36e..de0a08d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ C-style structs for Python [![PyPI](https://img.shields.io/pypi/pyversions/cstruct.svg)](https://pypi.org/project/cstruct) [![Downloads](https://pepy.tech/badge/cstruct/month)](https://pepy.tech/project/cstruct) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Known Vulnerabilities](https://snyk.io/test/github/andreax79/python-cstruct/badge.svg)](https://snyk.io/test/github/andreax79/python-cstruct) [![Documentation](https://readthedocs.org/projects/python-cstruct/badge/?version=latest)](https://python-cstruct.readthedocs.io/en/latest/) diff --git a/cstruct/__init__.py b/cstruct/__init__.py index b549276..1cf8e4f 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -28,21 +28,22 @@ __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union + +from .abstract import AbstractCEnum, AbstractCStruct, CStructMeta from .base import ( - LITTLE_ENDIAN, BIG_ENDIAN, + CHAR_ZERO, + DEFINES, + ENUMS, + LITTLE_ENDIAN, NATIVE_ORDER, STRUCTS, - ENUMS, - DEFINES, TYPEDEFS, - CHAR_ZERO, ) -from .abstract import CStructMeta, AbstractCStruct, AbstractCEnum -from .cstruct import CStruct from .c_parser import parse_struct_def -from .mem_cstruct import MemCStruct from .cenum import CEnum +from .cstruct import CStruct +from .mem_cstruct import MemCStruct from .native_types import get_native_type __all__ = [ diff --git a/cstruct/abstract.py b/cstruct/abstract.py index b1f6e35..896bb50 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -24,18 +24,19 @@ # IN THE SOFTWARE. # +import hashlib +import struct from abc import ABCMeta from collections import OrderedDict -from typing import Any, BinaryIO, List, Dict, Optional, Type, Tuple, Union -import hashlib +from enum import EnumMeta, IntEnum, _EnumDict from io import StringIO -from enum import IntEnum, EnumMeta, _EnumDict -import struct -from .base import STRUCTS, ENUMS, DEFAULT_ENUM_SIZE, ENUM_SIZE_TO_C_TYPE -from .c_parser import parse_struct, parse_struct_def, parse_enum_def, parse_enum, Tokens -from .field import calculate_padding, FieldType -from .native_types import get_native_type +from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Type, Union + +from .base import DEFAULT_ENUM_SIZE, ENUM_SIZE_TO_C_TYPE, ENUMS, STRUCTS +from .c_parser import Tokens, parse_enum, parse_enum_def, parse_struct, parse_struct_def from .exceptions import CStructException, ParserError +from .field import FieldType, calculate_padding +from .native_types import get_native_type __all__ = ["CStructMeta", "AbstractCStruct", "CEnumMeta", "AbstractCEnum"] diff --git a/cstruct/base.py b/cstruct/base.py index 1ee83b3..fc3a29c 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -22,10 +22,10 @@ # IN THE SOFTWARE. # -from typing import Any, Dict, Type, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, Type if TYPE_CHECKING: - from .abstract import AbstractCStruct, AbstractCEnum + from .abstract import AbstractCEnum, AbstractCStruct __all__ = [ "LITTLE_ENDIAN", diff --git a/cstruct/c_expr.py b/cstruct/c_expr.py index b7d0796..b2d16c6 100644 --- a/cstruct/c_expr.py +++ b/cstruct/c_expr.py @@ -24,7 +24,8 @@ import ast import operator -from typing import Any, Callable, Dict, Union, Type, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Callable, Dict, Type, Union + from .base import DEFINES, STRUCTS from .exceptions import EvalError diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index a6865bf..5b15014 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -24,15 +24,16 @@ import re from collections import OrderedDict -from typing import Union, Optional, Any, Dict, List, Type, TYPE_CHECKING -from .base import DEFINES, ENUMS, TYPEDEFS, STRUCTS -from .field import calculate_padding, Kind, FieldType +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union + +from .base import DEFINES, ENUMS, STRUCTS, TYPEDEFS from .c_expr import c_eval from .exceptions import CStructException, ParserError +from .field import FieldType, Kind, calculate_padding from .native_types import get_native_type if TYPE_CHECKING: - from .abstract import AbstractCStruct, AbstractCEnum + from .abstract import AbstractCEnum, AbstractCStruct __all__ = ["parse_struct", "parse_struct_def", "parse_enum_def", "Tokens"] diff --git a/cstruct/cstruct.py b/cstruct/cstruct.py index 78dd801..31c76fa 100644 --- a/cstruct/cstruct.py +++ b/cstruct/cstruct.py @@ -23,8 +23,9 @@ # from typing import List, Optional -from .base import CHAR_ZERO + from .abstract import AbstractCStruct +from .base import CHAR_ZERO class CStruct(AbstractCStruct): diff --git a/cstruct/field.py b/cstruct/field.py index bd9b3e3..1c30a35 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -25,10 +25,11 @@ import copy import struct from enum import Enum -from typing import Optional, Any, List, Type, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, List, Optional, Type + from .base import NATIVE_ORDER -from .native_types import get_native_type from .exceptions import ParserError +from .native_types import get_native_type if TYPE_CHECKING: from .abstract import AbstractCStruct diff --git a/cstruct/mem_cstruct.py b/cstruct/mem_cstruct.py index fcbf41f..66578f9 100644 --- a/cstruct/mem_cstruct.py +++ b/cstruct/mem_cstruct.py @@ -22,9 +22,10 @@ # IN THE SOFTWARE. # -from typing import Any, List, Optional import ctypes import struct +from typing import Any, List, Optional + from .abstract import AbstractCStruct diff --git a/cstruct/native_types.py b/cstruct/native_types.py index 6a60f6c..f1e205c 100644 --- a/cstruct/native_types.py +++ b/cstruct/native_types.py @@ -24,7 +24,7 @@ import struct from abc import ABCMeta -from typing import Any, Dict, Type, Tuple +from typing import Any, Dict, Tuple, Type __all__ = [ "get_native_type", diff --git a/examples/dir.py b/examples/dir.py index 0cde462..8002067 100644 --- a/examples/dir.py +++ b/examples/dir.py @@ -1,8 +1,9 @@ #!/usr/bin/env python -import cstruct import ctypes import sys +import cstruct + libc = ctypes.cdll.LoadLibrary("libc.so.6") # opendir libc.opendir.argtypes = [ctypes.c_char_p] diff --git a/examples/fdisk.py b/examples/fdisk.py index fdb352c..2054f3e 100644 --- a/examples/fdisk.py +++ b/examples/fdisk.py @@ -1,9 +1,10 @@ #!/usr/bin/env python -from pathlib import Path import argparse -import cstruct import sys +from pathlib import Path + +import cstruct UNITS = ['B', 'K', 'M', 'G', 'T'] SECTOR_SIZE = 512 diff --git a/examples/flexible_array.py b/examples/flexible_array.py index 4f8c28a..2664683 100644 --- a/examples/flexible_array.py +++ b/examples/flexible_array.py @@ -1,9 +1,10 @@ #!/usr/bin/env python import random -from cstruct import MemCStruct from pathlib import Path +from cstruct import MemCStruct + class FlexArray(MemCStruct): __def__ = """ diff --git a/examples/who.py b/examples/who.py index 0569cd1..7737de7 100644 --- a/examples/who.py +++ b/examples/who.py @@ -1,10 +1,11 @@ #!/usr/bin/env python -from cstruct import parse, getdef, MemCStruct, NATIVE_ORDER -from pathlib import Path import argparse import sys import time +from pathlib import Path + +from cstruct import NATIVE_ORDER, MemCStruct, getdef, parse DEFAULT_FILENAME = "/var/run/utmp" diff --git a/pyproject.toml b/pyproject.toml index 074200f..ccb95b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,3 +10,6 @@ source = ["cstruct"] [tool.coverage.report] exclude_lines = [ "# pragma: no cover", "if TYPE_CHECKING:" ] + +[tool.isort] +profile = "black" diff --git a/requirements-dev.txt b/requirements-dev.txt index eaeedf2..ea2ede6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,14 +1,15 @@ +black +build coverage[toml] -pytest-cov flake8 -mypy -tox -black -twine<3.4 -pytest +isort +markdown_include mkdocs -mkdocstrings[python] mkdocs-material -markdown_include -build +mkdocstrings[python] +mypy +pytest +pytest-cov setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability +tox +twine<3.4 diff --git a/tests/test_alignment.py b/tests/test_alignment.py index 609abf5..3895a2f 100644 --- a/tests/test_alignment.py +++ b/tests/test_alignment.py @@ -26,7 +26,8 @@ # ***************************************************************************** import sys -from cstruct import sizeof, typedef, define, CStruct, NATIVE_ORDER + +from cstruct import NATIVE_ORDER, CStruct, define, sizeof, typedef IS_64BITS = sys.maxsize > 2**32 diff --git a/tests/test_c_expr.py b/tests/test_c_expr.py index da8c89b..fe89dcb 100644 --- a/tests/test_c_expr.py +++ b/tests/test_c_expr.py @@ -25,7 +25,7 @@ # # ***************************************************************************** -from cstruct import parse, getdef, define +from cstruct import define, getdef, parse from cstruct.c_expr import c_eval diff --git a/tests/test_cenum.py b/tests/test_cenum.py index bea6aa2..4e9432c 100644 --- a/tests/test_cenum.py +++ b/tests/test_cenum.py @@ -1,7 +1,9 @@ +from enum import Enum + import pytest + import cstruct from cstruct import CEnum -from enum import Enum class Dummy(CEnum): diff --git a/tests/test_cstruct.py b/tests/test_cstruct.py index ae1d060..47594bf 100644 --- a/tests/test_cstruct.py +++ b/tests/test_cstruct.py @@ -25,12 +25,14 @@ # # ***************************************************************************** -import pytest -import cstruct -from cstruct import sizeof, typedef import io import os from pathlib import Path + +import pytest + +import cstruct +from cstruct import sizeof, typedef from cstruct.exceptions import ParserError MBR_DATA = (Path(__file__).parent.parent / "mbr").read_bytes() diff --git a/tests/test_define.py b/tests/test_define.py index 6a5a74e..e5d6c8f 100644 --- a/tests/test_define.py +++ b/tests/test_define.py @@ -26,9 +26,11 @@ # ***************************************************************************** import sys + import pytest + import cstruct -from cstruct import define, undef, sizeof, typedef +from cstruct import define, sizeof, typedef, undef from cstruct.exceptions import ParserError IS_64BITS = sys.maxsize > 2**32 diff --git a/tests/test_get_type.py b/tests/test_get_type.py index 72cb159..dd4b886 100644 --- a/tests/test_get_type.py +++ b/tests/test_get_type.py @@ -26,6 +26,7 @@ # ***************************************************************************** import pytest + import cstruct diff --git a/tests/test_memcstruct.py b/tests/test_memcstruct.py index bac47c1..603083b 100644 --- a/tests/test_memcstruct.py +++ b/tests/test_memcstruct.py @@ -25,11 +25,13 @@ # # ***************************************************************************** +import os +from pathlib import Path + import pytest + import cstruct from cstruct import sizeof, typedef -import os -from pathlib import Path from cstruct.exceptions import ParserError MBR_DATA = (Path(__file__).parent.parent / "mbr").read_bytes() diff --git a/tests/test_nested.py b/tests/test_nested.py index c5a877b..74b2b31 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -26,11 +26,11 @@ # ***************************************************************************** import pytest + import cstruct from cstruct import sizeof from cstruct.exceptions import ParserError - INVALID_ANONYMOUS = """ struct NestedStruct { struct { diff --git a/tests/test_pickle.py b/tests/test_pickle.py index 14789a0..29425c8 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import pickle + import cstruct diff --git a/tests/test_typdef.py b/tests/test_typdef.py index ca8a564..e0bb408 100644 --- a/tests/test_typdef.py +++ b/tests/test_typdef.py @@ -25,7 +25,7 @@ # # ***************************************************************************** -from cstruct import MemCStruct, NATIVE_ORDER +from cstruct import NATIVE_ORDER, MemCStruct from cstruct.base import TYPEDEFS diff --git a/tests/test_union.py b/tests/test_union.py index a13e97f..aa4b543 100644 --- a/tests/test_union.py +++ b/tests/test_union.py @@ -25,9 +25,10 @@ # # ***************************************************************************** +import struct + import cstruct from cstruct import sizeof -import struct class Position(cstruct.MemCStruct): From 1994571985bb5c93899ee75664e884133b054162 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 8 Jan 2024 15:22:35 +0100 Subject: [PATCH 65/78] fix struct in struct array parsing --- cstruct/c_parser.py | 51 ++++++++++++++++++++++---------------- tests/test_nested.py | 59 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 5b15014..21a6488 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -24,7 +24,7 @@ import re from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union from .base import DEFINES, ENUMS, STRUCTS, TYPEDEFS from .c_expr import c_eval @@ -101,6 +101,30 @@ def __str__(self) -> str: return str(self.tokens) +def parse_length(tokens: Tokens, next_token: str, vlen: int, flexible_array: bool) -> Tuple[str, int, bool]: + t = next_token.split("[") + if len(t) != 2: + raise ParserError(f"Error parsing: `{next_token}`") + next_token = t[0].strip() + vlen_part = t[1] + vlen_expr = [] + while not vlen_part.endswith("]"): + vlen_expr.append(vlen_part.split("]")[0].strip()) + vlen_part = tokens.pop() + t_vlen = vlen_part.split("]")[0].strip() + vlen_expr.append(vlen_part.split("]")[0].strip()) + t_vlen = " ".join(vlen_expr) + if not t_vlen: + flexible_array = True + vlen = 0 + else: + try: + vlen = c_eval(t_vlen) + except (ValueError, TypeError): + vlen = int(t_vlen) + return next_token, vlen, flexible_array + + def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Optional[str], offset: int) -> "FieldType": if len(tokens) < 2: raise ParserError("Parsing error") @@ -124,26 +148,7 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt c_type = "void *" # parse length if "[" in next_token: - t = next_token.split("[") - if len(t) != 2: - raise ParserError(f"Error parsing: `{next_token}`") - next_token = t[0].strip() - vlen_part = t[1] - vlen_expr = [] - while not vlen_part.endswith("]"): - vlen_expr.append(vlen_part.split("]")[0].strip()) - vlen_part = tokens.pop() - t_vlen = vlen_part.split("]")[0].strip() - vlen_expr.append(vlen_part.split("]")[0].strip()) - t_vlen = " ".join(vlen_expr) - if not t_vlen: - flexible_array = True - vlen = 0 - else: - try: - vlen = c_eval(t_vlen) - except (ValueError, TypeError): - vlen = int(t_vlen) + next_token, vlen, flexible_array = parse_length(tokens, next_token, vlen, flexible_array) tokens.push(next_token) # resolve typedefs while c_type in TYPEDEFS: @@ -421,6 +426,10 @@ def parse_struct( raise ParserError(f"Duplicate member `{vname}`") if vname in dir(__cls__): raise ParserError(f"Invalid reserved member name `{vname}`") + # parse length + if "[" in vname: + vname, field_type.vlen, field_type.flexible_array = parse_length(tokens, vname, 1, flexible_array) + flexible_array = flexible_array or field_type.flexible_array # anonymous nested union if vname == ";" and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): # add the anonymous struct fields to the parent diff --git a/tests/test_nested.py b/tests/test_nested.py index 74b2b31..e527180 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -70,6 +70,17 @@ class NestedUnion(cstruct.MemCStruct): """ +class NestedStructArr(cstruct.MemCStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct NestedStructArr { + struct b { + int c; + } bval[10]; + }; + """ + + class NestedAnonymousUnion(cstruct.MemCStruct): __byte_order__ = cstruct.LITTLE_ENDIAN __def__ = """ @@ -332,3 +343,51 @@ def test_nested_anonymous_struct_offset(): assert o.aaa.pack() == b'3333' assert o.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" assert o.b_op.inspect() == "00000000 e6 07 00 |... |\n" + + +def test_nested_struct_array(): + Nested = cstruct.parse( + """ + struct Nested { + struct b { + int c; + } bval; + int a; + }; + """, + __byte_order__=cstruct.LITTLE_ENDIAN, + ) + assert len(Nested) == 8 + t = Nested() + assert "a" in t.__fields_types__ + assert "bval" in t.__fields_types__ + + NestedArray = cstruct.parse( + """ + struct NestedArray { + struct b { + int c; + } bval[2]; + int a; + }; + """, + __byte_order__=cstruct.LITTLE_ENDIAN, + ) + t = NestedArray() + assert "a" in t.__fields_types__ + assert "bval" in t.__fields_types__ + assert len(NestedArray) > len(Nested) + t.bval[0].c = 10 + t.bval[1].c = 11 + assert t.bval[0].c == 10 + assert t.bval[1].c == 11 + assert len(t.bval) == 2 + + assert len(NestedStructArr) == 40 + t = NestedStructArr() + assert "bval" in t.__fields_types__ + t.bval[0].c = 10 + t.bval[1].c = 11 + assert t.bval[0].c == 10 + assert t.bval[1].c == 11 + assert len(t.bval) == 10 From 19bbf493999e2ad9b4d7b094ddbdef5e79f6f60d Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Mon, 8 Jan 2024 15:40:59 +0100 Subject: [PATCH 66/78] version 5.3 --- .gitignore | 1 + changelog.txt | 96 +++++++++++++++------------------------------ cstruct/__init__.py | 2 +- 3 files changed, 34 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index ee62e3d..553e4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ nosetests.xml .mypy_cache pyvenv.cfg site/ +__pycache__ diff --git a/changelog.txt b/changelog.txt index 6aece75..8a7f723 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,16 +1,12 @@ -# python-cstruct changelog +# Changelog -## 1.0 - -2013-08-19 +## [1.0] - 2013-08-19 ### Added - initial version -## 1.2 - -2017-05-18 +## [1.2] - 2017-05-18 ### Improved @@ -22,49 +18,37 @@ - who.py example - a changelog :) -## 1.3 - -2017-05-21 +## [1.3] - 2017-05-21 ### Fix - default value fix -## 1.4 - -2017-06-02 +## [1.4] - 2017-06-02 ### Fix - default value fix -## 1.5 - -2017-07-22 +## [1.5] - 2017-07-22 ### Fix - compatibiliy fix -## 1.6 - -2017-12-12 +## [1.6] - 2017-12-12 ### Fix - fixed size of 64-bit integers, they now have 64 bits, not 32 -## 1.7 - -2018-03-14 +## [1.7] - 2018-03-14 ### Improved - add support for // comments -## 1.8 - -2018-10-30 +## [1.8] - 2018-10-30 ### Improved @@ -75,9 +59,7 @@ - fix Python 2.5 support in main module - examples fix -## 1.9 - -2019-07-09 +## [1.9] - 2019-07-09 ### Improved @@ -88,17 +70,13 @@ - flexible array parsing - union initial support -## 2.0 - -2020-04-11 +## [2.0] - 2020-04-11 ### Improved - drop Python 2 support -## 2.1 - -2020-10-09 +## [2.1] - 2020-10-09 ### Improved @@ -106,9 +84,7 @@ - Python 3.9 support - Github workfows -## 2.2 - -2022-08-23 +## [2.2] - 2022-08-23 ### Fix @@ -120,41 +96,31 @@ - pytest - black code style -## 2.3 - -2022-09-01 +## [2.3] - 2022-09-01 ### Fix - Fix compare with None -## 3.0 - -2022-09-05 +## [3.0] - 2022-09-05 ### Added - Flexible array support -## 3.1 - -2022-09-13 +## [3.1] - 2022-09-13 ### Added - Make CStruct/MemCStruct Pickle Friendly -## 3.2 - -2022-10-23 +## [3.2] - 2022-10-23 ### Fix - Fix padding tests on 32bit architectures -## 3.3 - -2022-10-24 +## [3.3] - 2022-10-24 ### Added @@ -164,9 +130,7 @@ - Fix padding tests on 32bit architectures -## 4.0 - -2022-11-01 +## [4.0] - 2022-11-01 ### Added @@ -176,9 +140,7 @@ - Python 3.11 support -## 5.0 - -2022-11-12 +## [5.0] - 2022-11-12 ### Added @@ -191,9 +153,7 @@ - Documentation and examples - Restructure setup -## 5.1 - -2022-11-20 +## [5.1] - 2022-11-20 ### Improved @@ -205,12 +165,20 @@ - Add native type test - dir.py example -## 5.2 - -2022-11-23 +## [5.2] - 2022-11-23 ### Fix - nested struct unpack fix - nested anonymous union offset fix - inspect offset for nested struct/union fix + +## [5.3] - 2024-01-08 + +### Fix + +- fix struct in struct array parsing + +### Improved + +- Python 3.12 support diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 1cf8e4f..580b400 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "5.2" +__version__ = "5.3" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union From 1236e1e3be91177ed80538cb0a2508ff05fcff0d Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Fri, 27 Sep 2024 13:00:50 +0200 Subject: [PATCH 67/78] codespell --- Makefile | 6 +++++- cstruct/abstract.py | 6 +++--- cstruct/c_parser.py | 2 +- cstruct/cstruct.py | 2 +- cstruct/field.py | 2 +- cstruct/mem_cstruct.py | 2 +- requirements-dev.txt | 1 + 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 8986a88..5a2e117 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,10 @@ help: @echo - make typecheck -- Typecheck @echo - make venv ------- Create virtual environment +.PHONY: isort +codespell: + @codespell -w cstruct tests examples setup.py + .PHONY: isort isort: @isort --profile black cstruct tests examples setup.py @@ -46,6 +50,6 @@ typecheck: mypy --strict --no-warn-unused-ignores cstruct venv: - python3 -m virtualenv . + python3 -m venv . || python3 -m virtualenv . . bin/activate; pip install -Ur requirements.txt . bin/activate; pip install -Ur requirements-dev.txt diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 896bb50..31a9ffb 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -80,13 +80,13 @@ class AbstractCStruct(metaclass=CStructMeta): __size__: int = 0 " Size in bytes " __fields__: List[str] = [] - " Struct/union fileds " + " Struct/union fields " __fields_types__: Dict[str, FieldType] " Dictionary mapping field names to types " __byte_order__: Optional[str] = None " Byte order " __alignment__: int = 0 - " Alignament " + " Alignment " __is_union__: bool = False " True if the class is an union, False if it is a struct " @@ -125,7 +125,7 @@ def parse( __is_union__: True for union, False for struct Returns: - cls: a new class mapping the defintion + cls: a new class mapping the definition """ cls_kargs: Dict[str, Any] = dict(kargs) if __byte_order__ is not None: diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 21a6488..8c24e56 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -436,7 +436,7 @@ def parse_struct( for nested_field_name, nested_field_type in field_type.ref.__fields_types__.items(): if nested_field_name in fields_types: raise ParserError(f"Duplicate member `{nested_field_name}`") - # set the corret offset + # set the correct offset nested_field_type = nested_field_type.copy() nested_field_type.base_offset = offset + nested_field_type.base_offset nested_field_type.offset = offset + nested_field_type.offset diff --git a/cstruct/cstruct.py b/cstruct/cstruct.py index 31c76fa..9e899d9 100644 --- a/cstruct/cstruct.py +++ b/cstruct/cstruct.py @@ -48,7 +48,7 @@ def unpack_from(self, buffer: Optional[bytes], offset: int = 0, flexible_array_l Args: buffer: bytes to be unpacked offset: optional buffer offset - flexible_array_length: optional flexible array lenght (number of elements) + flexible_array_length: optional flexible array length (number of elements) """ self.set_flexible_array_length(flexible_array_length) if buffer is None: diff --git a/cstruct/field.py b/cstruct/field.py index 1c30a35..3568b36 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -192,7 +192,7 @@ def native_format(self) -> str: try: return get_native_type(self.c_type).native_format except KeyError: - raise ParserError(f"Unknow type `{self.c_type}`") + raise ParserError(f"Unknown type `{self.c_type}`") elif self.is_enum: return self.ref.__native_format__ else: diff --git a/cstruct/mem_cstruct.py b/cstruct/mem_cstruct.py index 66578f9..a0aaf1d 100644 --- a/cstruct/mem_cstruct.py +++ b/cstruct/mem_cstruct.py @@ -66,7 +66,7 @@ def unpack_from(self, buffer: Optional[bytes], offset: int = 0, flexible_array_l Args: buffer: bytes to be unpacked offset: optional buffer offset - flexible_array_length: optional flexible array lenght (number of elements) + flexible_array_length: optional flexible array length (number of elements) """ self.set_flexible_array_length(flexible_array_length) self.__base__ = offset # Base offset diff --git a/requirements-dev.txt b/requirements-dev.txt index ea2ede6..ea86fe9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ black build +codespell coverage[toml] flake8 isort From 5bca4a1f756d9421fdb017d7fd8ffc0399c07c64 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Wed, 15 Jan 2025 22:42:54 +0100 Subject: [PATCH 68/78] Access to Python class attributes in struct definition --- .readthedocs.yaml | 9 ++++ README.md | 27 ++++++++++ changelog.txt | 10 ++++ cstruct/__init__.py | 4 +- cstruct/abstract.py | 20 +++++-- cstruct/base.py | 2 +- cstruct/c_expr.py | 50 ++++++++++++++++-- cstruct/c_parser.py | 45 +++++++++++----- cstruct/cstruct.py | 2 +- cstruct/exceptions.py | 7 ++- cstruct/field.py | 63 +++++++++++++++++----- cstruct/mem_cstruct.py | 6 +-- cstruct/native_types.py | 17 +++--- setup.cfg | 1 + tests/test_cstruct_var.py | 107 ++++++++++++++++++++++++++++++++++++++ 15 files changed, 321 insertions(+), 49 deletions(-) create mode 100644 tests/test_cstruct_var.py diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8774cd0..b7168c0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,16 @@ # .readthedocs.yaml version: 2 + +# Set the OS and Python version +build: + os: ubuntu-22.04 + tools: + python: "3.12" + mkdocs: configuration: mkdocs.yml + +# Declare the Python requirements required to build the documentation python: install: - requirements: requirements-dev.txt diff --git a/README.md b/README.md index de0a08d..771adbd 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,33 @@ pkg.length = 4 pkg.data = [10, 20, 30, 40] ``` +### Python object attributes + +In struct definition, you can access Python object attributes using `self`. +The value of expression accessing class attributes is evaluated at runtime. + +```python +class RT11DirectoryEntry(cstruct.CStruct): + + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct RT11DirectoryEntry { + uint8_t type; + uint8_t clazz; + uint16_t raw_filename1; + uint16_t raw_filename2; + uint16_t raw_extension; + uint16_t length; + uint8_t job; + uint8_t channel; + uint16_t raw_creation_date; + uint16_t extra_bytes[self.extra_bytes_len]; /* The size of the array is determined at runtime */ + }; + """ + + extra_bytes_len: int = 0 +``` + ### Pack and Unpack A code example illustrating how to use diff --git a/changelog.txt b/changelog.txt index 8a7f723..e0ff73f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -182,3 +182,13 @@ ### Improved - Python 3.12 support + +## [6.0] - 2025-01-16 + +### Added + +- access to Python class attributes in struct definition + +### Improved + +- Python 3.13 support diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 580b400..c86d726 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "5.3" +__version__ = "6.0" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union diff --git a/cstruct/abstract.py b/cstruct/abstract.py index 31a9ffb..378d3ce 100644 --- a/cstruct/abstract.py +++ b/cstruct/abstract.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -163,7 +161,7 @@ def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> Non flexible_array: Optional[FieldType] = [x for x in self.__fields_types__.values() if x.flexible_array][0] if flexible_array is None: raise CStructException("Flexible array not found in struct") - flexible_array.vlen = flexible_array_length + flexible_array.vlen_ex = flexible_array_length def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length: Optional[int] = None) -> bool: """ @@ -202,6 +200,17 @@ def pack(self) -> bytes: # pragma: no cover """ raise NotImplementedError + def pack_into(self, buffer: bytearray, offset: int = 0) -> None: + """ + Pack the structure data into a buffer + + Args: + buffer: target buffer (must be large enough to contain the packed structure) + offset: optional buffer offset + """ + tmp = self.pack() + buffer[offset : offset + len(tmp)] = tmp + def clear(self) -> None: self.unpack(None) @@ -300,6 +309,9 @@ def __setstate__(self, state: bytes) -> bool: class CEnumMeta(EnumMeta): + __size__: int + __native_format__: str + class WrapperDict(_EnumDict): def __setitem__(self, key: str, value: Any) -> None: env = None diff --git a/cstruct/base.py b/cstruct/base.py index fc3a29c..ea5c223 100644 --- a/cstruct/base.py +++ b/cstruct/base.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # diff --git a/cstruct/c_expr.py b/cstruct/c_expr.py index b2d16c6..70edf46 100644 --- a/cstruct/c_expr.py +++ b/cstruct/c_expr.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -23,11 +23,12 @@ # import ast +import inspect import operator -from typing import TYPE_CHECKING, Any, Callable, Dict, Type, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, Union from .base import DEFINES, STRUCTS -from .exceptions import EvalError +from .exceptions import ContextNotFound, EvalError if TYPE_CHECKING: from .abstract import AbstractCStruct @@ -65,7 +66,36 @@ def c_eval(expr: str) -> Union[int, float]: raise EvalError +def eval_attribute_node(node: ast.Attribute) -> Union[int, float]: + """ + Evaluate node attribute, e.g. 'self.x' + Only 'self' is allowed. The attribute must be a number. + + Args: + node: attribute node + + Returns: + result: the attribute value + + Raises: + EvalError: expression result is not a number, or not self attribute + ContextNotFound: context is not defined + """ + if not node.value or node.value.id != "self": # type: ignore + raise EvalError("only self is allowed") + context = get_cstruct_context() + if context is None: + raise ContextNotFound("context is not defined") + result = getattr(context, node.attr) + if not isinstance(result, (int, float)): + raise EvalError("expression result is not a number") + return result + + def eval_node(node: ast.stmt) -> Union[int, float]: + if isinstance(node, ast.Attribute): + return eval_attribute_node(node) + handler = OPS[type(node)] result = handler(node) if isinstance(result, bool): # convert bool to int @@ -116,6 +146,20 @@ def eval_call(node) -> Union[int, float]: raise KeyError(node.func.id) +def get_cstruct_context() -> Optional["AbstractCStruct"]: + """ + Get the calling CStruct instance from the stack (if any) + """ + from .abstract import AbstractCStruct + + stack = inspect.stack() + for frame in stack: + caller_self = frame.frame.f_locals.get("self") + if isinstance(caller_self, AbstractCStruct): + return caller_self + return None + + try: Constant = ast.Constant except AttributeError: # python < 3.8 diff --git a/cstruct/c_parser.py b/cstruct/c_parser.py index 8c24e56..2a95705 100644 --- a/cstruct/c_parser.py +++ b/cstruct/c_parser.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -24,11 +24,21 @@ import re from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + List, + Optional, + Tuple, + Type, + Union, +) from .base import DEFINES, ENUMS, STRUCTS, TYPEDEFS from .c_expr import c_eval -from .exceptions import CStructException, ParserError +from .exceptions import CStructException, EvalError, ParserError from .field import FieldType, Kind, calculate_padding from .native_types import get_native_type @@ -41,7 +51,7 @@ SPACES = [" ", "\t", "\n"] -class Tokens(object): +class Tokens: def __init__(self, text: str) -> None: # remove the comments text = re.sub(r"//.*?$|/\*.*?\*/", "", text, flags=re.S | re.MULTILINE) @@ -59,7 +69,7 @@ def __init__(self, text: str) -> None: text = "\n".join(lines) self.tokens = self.tokenize(text) - def tokenize(self, text) -> List[str]: + def tokenize(self, text: str) -> List[str]: tokens: List[str] = [] t: List[str] = [] for c in text: @@ -72,7 +82,7 @@ def tokenize(self, text) -> List[str]: else: t.append(c) if t: - tokens.append(t.getvalue()) + tokens.append("".join(t)) return tokens def pop(self) -> str: @@ -101,7 +111,8 @@ def __str__(self) -> str: return str(self.tokens) -def parse_length(tokens: Tokens, next_token: str, vlen: int, flexible_array: bool) -> Tuple[str, int, bool]: +def parse_length(tokens: Tokens, next_token: str, flexible_array: bool) -> Tuple[str, Union[int, Callable[[], int]], bool]: + # Extract t_vlen t = next_token.split("[") if len(t) != 2: raise ParserError(f"Error parsing: `{next_token}`") @@ -114,14 +125,19 @@ def parse_length(tokens: Tokens, next_token: str, vlen: int, flexible_array: boo t_vlen = vlen_part.split("]")[0].strip() vlen_expr.append(vlen_part.split("]")[0].strip()) t_vlen = " ".join(vlen_expr) + # Evaluate t_vlen + vlen: Union[int, Callable[[], int]] if not t_vlen: + # If the length expression is empty, this is a flex array flexible_array = True vlen = 0 else: + # Evaluate the length expression + # If the length expression is not a constant, it is evaluated at runtime try: - vlen = c_eval(t_vlen) - except (ValueError, TypeError): - vlen = int(t_vlen) + vlen = int(c_eval(t_vlen)) + except EvalError: + vlen = lambda: int(c_eval(t_vlen)) return next_token, vlen, flexible_array @@ -133,7 +149,7 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt if c_type in ["signed", "unsigned", "struct", "union", "enum"] and len(tokens) > 1: c_type = c_type + " " + tokens.pop() - vlen = 1 + vlen: Union[int, Callable[[], int]] = 1 flexible_array = False if not c_type.endswith("{"): @@ -148,20 +164,21 @@ def parse_type(tokens: Tokens, __cls__: Type["AbstractCStruct"], byte_order: Opt c_type = "void *" # parse length if "[" in next_token: - next_token, vlen, flexible_array = parse_length(tokens, next_token, vlen, flexible_array) + next_token, vlen, flexible_array = parse_length(tokens, next_token, flexible_array) tokens.push(next_token) # resolve typedefs while c_type in TYPEDEFS: c_type = TYPEDEFS[c_type] # calculate fmt + ref: Union[None, Type[AbstractCEnum], Type[AbstractCStruct]] if c_type.startswith("struct ") or c_type.startswith("union "): # struct/union c_type, tail = c_type.split(" ", 1) kind = Kind.STRUCT if c_type == "struct" else Kind.UNION if tokens.get() == "{": # Named nested struct tokens.push(tail) tokens.push(c_type) - ref: Union[Type[AbstractCEnum], Type[AbstractCStruct]] = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) + ref = __cls__.parse(tokens, __name__=tail, __byte_order__=byte_order) elif tail == "{": # Unnamed nested struct tokens.push(tail) tokens.push(c_type) @@ -428,7 +445,7 @@ def parse_struct( raise ParserError(f"Invalid reserved member name `{vname}`") # parse length if "[" in vname: - vname, field_type.vlen, field_type.flexible_array = parse_length(tokens, vname, 1, flexible_array) + vname, field_type.vlen_ex, field_type.flexible_array = parse_length(tokens, vname, flexible_array) flexible_array = flexible_array or field_type.flexible_array # anonymous nested union if vname == ";" and field_type.ref is not None and (__is_union__ or field_type.ref.__is_union__): diff --git a/cstruct/cstruct.py b/cstruct/cstruct.py index 9e899d9..66be23f 100644 --- a/cstruct/cstruct.py +++ b/cstruct/cstruct.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # diff --git a/cstruct/exceptions.py b/cstruct/exceptions.py index b5546b4..cf496ae 100644 --- a/cstruct/exceptions.py +++ b/cstruct/exceptions.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -27,6 +27,7 @@ "CStructException", "ParserError", "EvalError", + "ContextNotFound", ] @@ -44,3 +45,7 @@ class ParserError(CStructException): class EvalError(CStructException): pass + + +class ContextNotFound(EvalError): + pass diff --git a/cstruct/field.py b/cstruct/field.py index 3568b36..2d0bb16 100644 --- a/cstruct/field.py +++ b/cstruct/field.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -23,12 +23,13 @@ # import copy +import inspect import struct from enum import Enum -from typing import TYPE_CHECKING, Any, List, Optional, Type +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union from .base import NATIVE_ORDER -from .exceptions import ParserError +from .exceptions import ContextNotFound, ParserError from .native_types import get_native_type if TYPE_CHECKING: @@ -49,6 +50,20 @@ def calculate_padding(byte_order: Optional[str], alignment: int, pos: int) -> in return 0 +def get_cstruct_context() -> Optional["AbstractCStruct"]: + """ + Get the current CStruct context (instance) from the stack + """ + from .abstract import AbstractCStruct + + stack = inspect.stack() + for frame in stack: + caller_self = frame.frame.f_locals.get("self") + if isinstance(caller_self, AbstractCStruct): + return caller_self + return None + + class Kind(Enum): """ Field type @@ -64,7 +79,7 @@ class Kind(Enum): "Enum type" -class FieldType(object): +class FieldType: """ Struct/Union field @@ -72,18 +87,27 @@ class FieldType(object): kind (Kind): struct/union/native c_type (str): field type ref (AbstractCStruct): struct/union class ref - vlen (int): number of elements + vlen_ex (int|callable int): number of elements flexible_array (bool): True for flexible arrays offset (int): relative memory position of the field (relative to the struct) padding (int): padding """ + kind: Kind + c_type: str + ref: Optional[Type["AbstractCStruct"]] + vlen_ex: Union[int, Callable[[], int]] + flexible_array: bool + byte_order: Optional[str] + offset: int + padding: int + def __init__( self, kind: Kind, c_type: str, ref: Optional[Type["AbstractCStruct"]], - vlen: int, + vlen_ex: Union[int, Callable[[], int]], flexible_array: bool, byte_order: Optional[str], offset: int, @@ -95,26 +119,27 @@ def __init__( kind: struct/union/native c_type: field type ref: struct/union class ref - vlen: number of elements + vlen_ex: number of elements flexible_array: True for flexible arrays offset: relative memory position of the field (relative to the struct) """ self.kind = kind self.c_type = c_type self.ref = ref - self.vlen = vlen + self.vlen_ex = vlen_ex self.flexible_array = flexible_array self.byte_order = byte_order self.offset = self.base_offset = offset self.padding = 0 - def unpack_from(self, buffer: bytes, offset: int = 0) -> Any: + def unpack_from(self, buffer: bytes, offset: int = 0, context: Optional["AbstractCStruct"] = None) -> Any: """ Unpack bytes containing packed C structure data Args: buffer: bytes to be unpacked offset: optional buffer offset + context: context (cstruct instance) Returns: data: The unpacked data @@ -153,13 +178,24 @@ def pack(self, data: Any) -> bytes: bytes: The packed structure """ if self.flexible_array: - self.vlen = len(data) # set flexible array size + self.vlen_ex = len(data) # set flexible array size return struct.pack(self.fmt, *data) elif self.is_array: - return struct.pack(self.fmt, *data) + if self.vlen == 0: # empty array + return bytes() + else: + return struct.pack(self.fmt, *data) else: return struct.pack(self.fmt, data) + @property + def vlen(self) -> int: + "Number of elements" + try: + return self.vlen_ex() if callable(self.vlen_ex) else self.vlen_ex + except ContextNotFound: + return 0 + @property def is_array(self) -> bool: "True if field is an array/flexible array" @@ -202,7 +238,10 @@ def native_format(self) -> str: def fmt(self) -> str: "Field format prefixed by byte order (struct library format)" if self.is_native or self.is_enum: - fmt = (str(self.vlen) if self.vlen > 1 or self.flexible_array else "") + self.native_format + if self.vlen == 0: + fmt = "" + else: + fmt = (str(self.vlen) if self.vlen > 1 or self.flexible_array else "") + self.native_format else: # Struct/Union fmt = str(self.vlen * self.ref.sizeof()) + self.native_format if self.byte_order: diff --git a/cstruct/mem_cstruct.py b/cstruct/mem_cstruct.py index a0aaf1d..e457de6 100644 --- a/cstruct/mem_cstruct.py +++ b/cstruct/mem_cstruct.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -35,7 +35,7 @@ def __init__(self, values: List[Any], name: str, parent: Optional["MemCStruct"] self.name = name self.parent = parent - def __setitem__(self, key: int, value: List[Any]) -> None: # noqa: F811 + def __setitem__(self, key: int, value: List[Any]) -> None: # type: ignore super().__setitem__(key, value) # Notify the parent when a value is changed if self.parent is not None: @@ -136,7 +136,7 @@ def __setattr__(self, attr: str, value: Any) -> None: else: # native if field_type.flexible_array and len(value) != field_type.vlen: # flexible array size changed, resize the buffer - field_type.vlen = len(value) + field_type.vlen_ex = len(value) ctypes.resize(self.__mem__, self.size + 1) addr = field_type.offset + self.__base__ self.memcpy(addr, field_type.pack(value), field_type.vsize) diff --git a/cstruct/native_types.py b/cstruct/native_types.py index f1e205c..fd20f47 100644 --- a/cstruct/native_types.py +++ b/cstruct/native_types.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2013-2019 Andrea Bonomi +# Copyright (c) 2013-2025 Andrea Bonomi # # Published under the terms of the MIT license. # @@ -54,10 +54,10 @@ ] -NATIVE_TYPES: Dict[str, "AbstractNativeType"] = {} +NATIVE_TYPES: Dict[str, Type["AbstractNativeType"]] = {} -def get_native_type(type_: str) -> "AbstractNativeType": +def get_native_type(type_: str) -> Type["AbstractNativeType"]: """ Get a base data type by name @@ -79,10 +79,6 @@ def get_native_type(type_: str) -> "AbstractNativeType": class NativeTypeMeta(ABCMeta): __size__: int = 0 " Size in bytes " - type_name: str = "" - " Type name " - native_format: str = "" - " Type format " def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]: if namespace.get("native_format"): @@ -92,7 +88,7 @@ def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[s native_format = None namespace["native_format"] = None namespace["__size__"] = None - new_class: Type[Any] = super().__new__(metacls, name, bases, namespace) + new_class: Type[AbstractNativeType] = super().__new__(metacls, name, bases, namespace) # type: ignore if namespace.get("type_name"): NATIVE_TYPES[namespace["type_name"]] = new_class return new_class @@ -108,6 +104,11 @@ def size(cls) -> int: class AbstractNativeType(metaclass=NativeTypeMeta): + type_name: str = "" + " Type name " + native_format: str = "" + " Type format " + def __str__(self) -> str: return self.type_name diff --git a/setup.cfg b/setup.cfg index 59d4b3f..f737c08 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,7 @@ classifiers = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 project_urls = Bug Tracker = http://github.com/andreax79/python-cstruct/issues Documentation = https://python-cstruct.readthedocs.io/en/latest/ diff --git a/tests/test_cstruct_var.py b/tests/test_cstruct_var.py new file mode 100644 index 0000000..4ad9c07 --- /dev/null +++ b/tests/test_cstruct_var.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# ***************************************************************************** +# +# Copyright (c) 2013-2025 Andrea Bonomi +# +# Published under the terms of the MIT license. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# ***************************************************************************** + +import cstruct +from cstruct import sizeof +from cstruct.c_expr import c_eval + + +class Struct0(cstruct.CStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct { + uint16_t a; + uint16_t b; + char c[6]; + } + """ + + +class Struct1(cstruct.CStruct): + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + #define X1 6 + struct { + uint16_t a; + uint16_t b; + char c[X1]; + } + """ + + +class Struct2(cstruct.CStruct): + c_len: int = 6 + + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct { + uint16_t a; + uint16_t b; + char c[self.c_len]; + } + """ + + +class Struct3(cstruct.MemCStruct): + c_len: int = 0 + + __byte_order__ = cstruct.LITTLE_ENDIAN + __def__ = """ + struct { + uint16_t a; + uint16_t b; + uint16_t c[self.c_len]; + } + """ + + +def test_v(): + assert c_eval('10') == 10 + + s0 = Struct0() + assert len(s0) == 10 + s1 = Struct1() + assert len(s1) == 10 + + assert sizeof(Struct2) == 4 + + s2 = Struct2() + assert len(s2) == 10 + s2.c_len = 10 + assert len(s2) == 14 + + for i in range(10): + s2.c_len = i + assert len(s2) == 4 + i + + assert sizeof(Struct3) == 4 + s3 = Struct3() + assert len(s3) == 4 + + for i in range(10): + s3.c_len = i + assert len(s3) == 4 + i * 2 From 5a8a7c6d6def9a03d72fb058f357676b2c38340d Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Tue, 18 Feb 2025 06:53:49 +0100 Subject: [PATCH 69/78] Update README.md --- README.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 771adbd..cd12111 100644 --- a/README.md +++ b/README.md @@ -161,11 +161,16 @@ class Packet(cstruct.MemCStruct): ### Byte Order, Size, and Padding -Suported byte orders: +Python-CStruct supports big-endian, little-endian, and native byte orders, which you can specify using: -* `cstruct.LITTLE_ENDIAN` - Little endian byte order, standard size, no padding -* `cstruct.BIG_ENDIAN` - Big endian byte order, standard size, no padding -* `cstruct.NATIVE_ORDER` - Native byte order, native size, padding +* `cstruct.LITTLE_ENDIAN` - Little endian byte order, standard size, no padding. +* `cstruct.BIG_ENDIAN` - Big endian byte order, standard size, no padding. +* `cstruct.NATIVE_ORDER` - Native byte order, native size, padding. Native byte order is big-endian or little-endian, depending on the host system. + +Standard size depends only on the format character while native size depends on the host system. +For native order, padding is automatically added to align the structure members. + +For more information, see the [struct - Byte Order, Size, and Padding](https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment) section. ```python class Native(cstruct.MemCStruct): @@ -321,6 +326,24 @@ class MBR(cstruct.MemCStruct): """ ``` +### Accessing Field Definitions + +Python-CStruct provides the `__fields_types__` attribute at the class level, which allows you to inspect the metadata of each field. +The dictionary contains each field's name as a key and its metadata as a value. +Each field has the following attributes: + +| Attribute | Description | +|------------------|------------| +| `kind` | Indicates whether the field is a primitive type or a nested struct. | +| `c_type` | The corresponding C type. | +| `ref` | If the field is a nested struct, this contains a reference to the class representing that struct. | +| `vlen_ex` | The number of elements in the field. | +| `flexible_array` | Indicates whether the field is a flexible array. | +| `byte_order` | The byte order of the field. | +| `offset` | The relative position of the field within the struct. | +| `padding` | The number of bytes added before this field for alignment. | + + ### Ispect memory The [`inspect`](https://python-cstruct.readthedocs.io/en/latest/api/abstract/#cstruct.abstract.AbstractCStruct.inspect) methods displays memory contents in hexadecimal. From 322424e541f2b1de5eb30de5fca6c7df9570781f Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Fri, 21 Mar 2025 23:00:28 +0100 Subject: [PATCH 70/78] version 6.1 - fix CStruct.pack() padding --- Makefile | 2 +- changelog.txt | 6 ++++ cstruct/__init__.py | 2 +- cstruct/cstruct.py | 4 +++ tests/test_padding.py | 71 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 tests/test_padding.py diff --git a/Makefile b/Makefile index 5a2e117..4cfe394 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ black: isort clean: -rm -rf build dist -rm -rf *.egg-info - -rm -rf bin lib share pyvenv.cfg + -rm -rf bin lib lib64 share include pyvenv.cfg coverage: @pytest --cov --cov-report=term-missing diff --git a/changelog.txt b/changelog.txt index e0ff73f..8d03ecf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -192,3 +192,9 @@ ### Improved - Python 3.13 support + +## [6.1] - 2025-03-21 + +### Fix + +- fix CStruct.pack() padding diff --git a/cstruct/__init__.py b/cstruct/__init__.py index c86d726..149b919 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "6.0" +__version__ = "6.1" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union diff --git a/cstruct/cstruct.py b/cstruct/cstruct.py index 66be23f..e0fee15 100644 --- a/cstruct/cstruct.py +++ b/cstruct/cstruct.py @@ -66,6 +66,10 @@ def pack(self) -> bytes: """ result: List[bytes] = [] for field, field_type in self.__fields_types__.items(): + # Add padding if needed + if field_type.padding: + result.append(CHAR_ZERO * field_type.padding) + if field_type.is_struct or field_type.is_union: if field_type.vlen == 1: # single struct v = getattr(self, field, field_type.ref()) diff --git a/tests/test_padding.py b/tests/test_padding.py new file mode 100644 index 0000000..da0a7cf --- /dev/null +++ b/tests/test_padding.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +import cstruct +from cstruct import LITTLE_ENDIAN, NATIVE_ORDER, sizeof + +DEFINITION = """ + struct struct1 { + char a[2]; + uint32_t b; + } +""" + + +class CStructNative(cstruct.CStruct): + __byte_order__ = NATIVE_ORDER + __def__ = DEFINITION + + +class MemCStructNative(cstruct.MemCStruct): + __byte_order__ = NATIVE_ORDER + __def__ = DEFINITION + + +class CStructLittleEndian(cstruct.CStruct): + __byte_order__ = LITTLE_ENDIAN + __def__ = DEFINITION + + +class MemCStructLittleEndian(cstruct.MemCStruct): + __byte_order__ = LITTLE_ENDIAN + __def__ = DEFINITION + + +def test_memcstruct_padding(): + for struct in (CStructNative, MemCStructNative): + data = b"\x41\x42\x00\x00\x01\x00\x00\x00" + assert sizeof(struct) == 8 + t = struct() + t.unpack(data) + assert t.a == b"AB" + assert t.b == 1 + buf = t.pack() + assert len(buf) == sizeof(struct) + assert buf == data + + t2 = struct() + t2.unpack(buf) + assert t2.a == b"AB" + assert t2.b == 1 + buf = t.pack() + assert len(buf) == sizeof(struct) + + +def test_no_padding(): + for struct in (CStructLittleEndian, MemCStructLittleEndian): + data = b"\x41\x42\x01\x00\x00\x00" + assert sizeof(struct) == 6 + t = struct() + t.unpack(data) + assert t.a == b"AB" + assert t.b == 1 + buf = t.pack() + assert len(buf) == sizeof(struct) + assert buf == data + + t2 = struct() + t2.unpack(buf) + assert t2.a == b"AB" + assert t2.b == 1 + buf = t.pack() + assert len(buf) == sizeof(struct) From cffe68912061bb945bc9a97a08a5babf0c4ebde4 Mon Sep 17 00:00:00 2001 From: Philipp Bartsch Date: Fri, 9 Jan 2026 19:27:59 +0100 Subject: [PATCH 71/78] make ast module usage compatible to python 3.14 Python 3.8 deprecated some AST nodes, which got removed in Python 3.14. See https://docs.python.org/3.14/whatsnew/3.14.html#id9 --- cstruct/c_expr.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cstruct/c_expr.py b/cstruct/c_expr.py index 70edf46..03e0f47 100644 --- a/cstruct/c_expr.py +++ b/cstruct/c_expr.py @@ -25,6 +25,7 @@ import ast import inspect import operator +import sys from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, Union from .base import DEFINES, STRUCTS @@ -160,18 +161,11 @@ def get_cstruct_context() -> Optional["AbstractCStruct"]: return None -try: - Constant = ast.Constant -except AttributeError: # python < 3.8 - Constant = ast.NameConstant - OPS: Dict[Type[ast.AST], Callable[[Any], Any]] = { ast.Expr: lambda node: eval_node(node.value), - ast.Num: lambda node: node.n, ast.Name: eval_get, ast.Call: eval_call, - Constant: lambda node: node.value, - ast.Str: lambda node: node.s, # python < 3.8 + ast.Constant: lambda node: node.value, # and/or ast.BoolOp: lambda node: OPS[type(node.op)](node), # and/or operator ast.And: lambda node: all(eval_node(x) for x in node.values), # && operator @@ -203,3 +197,7 @@ def get_cstruct_context() -> Optional["AbstractCStruct"]: ast.GtE: operator.ge, ast.LtE: operator.le, } + +if sys.version_info.major == 3 and sys.version_info.minor < 8: + OPS[ast.Num] = lambda node: node.n + OPS[ast.Str] = lambda node: node.s From 7044350dfbc860d6018aad449d530ef322fe121d Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 09:33:16 +0100 Subject: [PATCH 72/78] github tests with python 3.13 and 3.14 --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 41d40e3..20a2332 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,8 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" + - "3.14" runs-on: ubuntu-latest From 8f71a0c5670e43acc88debaba3f9d7158d09f704 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 14:16:02 +0100 Subject: [PATCH 73/78] add llms.txt --- llms.txt | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 llms.txt diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..ed5ea1d --- /dev/null +++ b/llms.txt @@ -0,0 +1,63 @@ +# llms.txt - Guidance for Large Language Models +# Project: Python-CStruct + +python-cstruct is a Python module for defining, parsing, and serializing +C-style binary structures using C syntax (`struct`, `union`, `enum`). +It allows users to describe structured binary layouts and convert +between raw bytes and Python objects. + +python-cstruct is a Python library for defining and parsing binary data using +C-style syntax (`struct`, `union`, `enum`). It converts C definitions into +Python classes that can pack and unpack raw bytes deterministically. + +## What This Project Is +- A binary parsing and serialization library +- Based on C syntax embedded as strings +- Produces Python objects with `.pack()` / `.unpack()` +- Suitable for files, protocols, and low-level data formats + +## What This Project Is NOT +- NOT a general Python–C FFI or ABI binding +- NOT a replacement for `ctypes.Structure` +- NOT an automatic structure inference tool + +## Core APIs & Concepts +- `MemCStruct`: base class for C `struct` / `union` definitions +- `CEnum`: base class for C `enum` definitions +- `cstruct.parse()`: parse C definitions at runtime +- Fields are accessed as Python attributes +- Layout is deterministic and explicit + +## Endianness +- Explicitly controlled via: + - `LITTLE_ENDIAN` + - `BIG_ENDIAN` + - `NATIVE_ORDER` +- Do NOT assume platform defaults + +## Typical Usage +1. Define a C struct/union/enum as a string +2. Bind it to a Python class or parse it dynamically +3. Pack Python objects into bytes or unpack bytes into objects +4. Access fields as attributes + +## Examples (Authoritative) +- MBR partition parsing: + https://python-cstruct.readthedocs.io/en/latest/examples/fdisk/ +- Logged users (`who`) example: + https://python-cstruct.readthedocs.io/en/latest/examples/who/ +- Flexible Array Member (FAM): + https://python-cstruct.readthedocs.io/en/latest/examples/flexible_array/ +- libc integration (ctypes): + https://python-cstruct.readthedocs.io/en/latest/examples/dir/ + +## API Reference (Source of Truth) +https://python-cstruct.readthedocs.io/en/latest/api/module/ + +## Guidance for Code Generation +- Use real API names (`MemCStruct`, `CEnum`, `cstruct.parse`) +- Always specify byte order when relevant +- Follow the official documentation if uncertain + +If there is any conflict between assumptions and the documentation or source +code, the documentation and source code take precedence. From ecd1d2a1e36b86ab751fe455c43e96a9037babd7 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 15:27:21 +0100 Subject: [PATCH 74/78] format with ruff --- README.md | 3 +-- examples/fdisk.py | 4 ++-- examples/who.py | 2 +- tests/test_cstruct_var.py | 2 +- tests/test_nested.py | 42 +++++++++++++++++++-------------------- 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index cd12111..0a6b276 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ C-style structs for Python [![PyPI version](https://badge.fury.io/py/cstruct.svg)](https://badge.fury.io/py/cstruct) [![PyPI](https://img.shields.io/pypi/pyversions/cstruct.svg)](https://pypi.org/project/cstruct) [![Downloads](https://pepy.tech/badge/cstruct/month)](https://pepy.tech/project/cstruct) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![Known Vulnerabilities](https://snyk.io/test/github/andreax79/python-cstruct/badge.svg)](https://snyk.io/test/github/andreax79/python-cstruct) [![Documentation](https://readthedocs.org/projects/python-cstruct/badge/?version=latest)](https://python-cstruct.readthedocs.io/en/latest/) diff --git a/examples/fdisk.py b/examples/fdisk.py index 2054f3e..b18c970 100644 --- a/examples/fdisk.py +++ b/examples/fdisk.py @@ -6,7 +6,7 @@ import cstruct -UNITS = ['B', 'K', 'M', 'G', 'T'] +UNITS = ["B", "K", "M", "G", "T"] SECTOR_SIZE = 512 TYPES = { 0x00: "Empty", @@ -129,7 +129,7 @@ def disk_signature_str(self): def print_info(self): print(f"Sector size: {cstruct.getdef('MBR_SIZE')}") - if self.signature != cstruct.getdef('MBR_BOOT_SIGNATURE'): + if self.signature != cstruct.getdef("MBR_BOOT_SIGNATURE"): print("Invalid MBR signature") print(f"Disk identifier: 0x{self.disk_signature_str}") diff --git a/examples/who.py b/examples/who.py index 7737de7..a8741b0 100644 --- a/examples/who.py +++ b/examples/who.py @@ -110,7 +110,7 @@ def __str__(self): return f"{self.user:<10s} {self.line:<12s} {self.time:<15s} {self.ut_pid:>15} {self.host:<8s}" def print_info(self, show_all): - if show_all or self.ut_type in (getdef('LOGIN_PROCESS'), getdef('USER_PROCESS')): + if show_all or self.ut_type in (getdef("LOGIN_PROCESS"), getdef("USER_PROCESS")): print(self) diff --git a/tests/test_cstruct_var.py b/tests/test_cstruct_var.py index 4ad9c07..95e24ef 100644 --- a/tests/test_cstruct_var.py +++ b/tests/test_cstruct_var.py @@ -80,7 +80,7 @@ class Struct3(cstruct.MemCStruct): def test_v(): - assert c_eval('10') == 10 + assert c_eval("10") == 10 s0 = Struct0() assert len(s0) == 10 diff --git a/tests/test_nested.py b/tests/test_nested.py index e527180..a95b0b8 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -255,21 +255,21 @@ def test_nested_struct_offset(): ) o = Op() - o.preamble = b'ciao_ciao' + o.preamble = b"ciao_ciao" o.magic = 3771778641802345472 o.u1.a_op.a = 2022 o.aaa.a = 0x33333333 - assert o.u1.b_op.a == b'\xe6' - assert o.u1.b_op.b == b'\x07' - assert o.u1.b_op.c == b'\x00' + assert o.u1.b_op.a == b"\xe6" + assert o.u1.b_op.b == b"\x07" + assert o.u1.b_op.c == b"\x00" assert o.__base__ == 0 assert o.u1.__base__ >= 10 assert o.u1.__base__ == o.u1.a_op.__base__ assert o.u1.__base__ == o.u1.b_op.__base__ assert o.aaa.__base__ > o.u1.__base__ - assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' - assert o.u1.pack() == b'\xe6\x07\x00\x00' - assert o.aaa.pack() == b'3333' + assert o.pack() == b"ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333" + assert o.u1.pack() == b"\xe6\x07\x00\x00" + assert o.aaa.pack() == b"3333" assert o.u1.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" assert o.u1.b_op.inspect() == "00000000 e6 07 00 |... |\n" @@ -306,41 +306,41 @@ def test_nested_anonymous_struct_offset(): ) o = Opu() - o.preamble = b'ciao_ciao' + o.preamble = b"ciao_ciao" o.magic = 3771778641802345472 o.__anonymous0.a_op.a = 2022 o.aaa.a = 0x33333333 - assert o.__anonymous0.b_op.a == b'\xe6' - assert o.__anonymous0.b_op.b == b'\x07' - assert o.__anonymous0.b_op.c == b'\x00' + assert o.__anonymous0.b_op.a == b"\xe6" + assert o.__anonymous0.b_op.b == b"\x07" + assert o.__anonymous0.b_op.c == b"\x00" assert o.__base__ == 0 assert o.__anonymous0.__base__ >= 10 assert o.__anonymous0.__base__ == o.__anonymous0.a_op.__base__ assert o.__anonymous0.__base__ == o.__anonymous0.b_op.__base__ assert o.aaa.__base__ > o.__anonymous0.__base__ - assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' - assert o.__anonymous0.pack() == b'\xe6\x07\x00\x00' - assert o.aaa.pack() == b'3333' + assert o.pack() == b"ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333" + assert o.__anonymous0.pack() == b"\xe6\x07\x00\x00" + assert o.aaa.pack() == b"3333" assert o.__anonymous0.inspect() == "00000000 e6 07 00 00 |.... |\n" assert o.__anonymous0.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" assert o.__anonymous0.b_op.inspect() == "00000000 e6 07 00 |... |\n" o = Opu() - o.preamble = b'ciao_ciao' + o.preamble = b"ciao_ciao" o.magic = 3771778641802345472 o.a_op.a = 2022 o.aaa.a = 0x33333333 - assert o.b_op.a == b'\xe6' - assert o.b_op.b == b'\x07' - assert o.b_op.c == b'\x00' + assert o.b_op.a == b"\xe6" + assert o.b_op.b == b"\x07" + assert o.b_op.c == b"\x00" assert o.__base__ == 0 assert o.__anonymous0.__base__ >= 10 assert o.__anonymous0.__base__ == o.a_op.__base__ assert o.__anonymous0.__base__ == o.b_op.__base__ assert o.aaa.__base__ > o.__base__ - assert o.pack() == b'ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333' - assert o.a_op.pack() == b'\xe6\x07\x00\x00' - assert o.aaa.pack() == b'3333' + assert o.pack() == b"ciao_ciao\x00\x00\xbc\x08\xe4\xb0\x0cX4\xe6\x07\x00\x003333" + assert o.a_op.pack() == b"\xe6\x07\x00\x00" + assert o.aaa.pack() == b"3333" assert o.a_op.inspect() == "00000000 e6 07 00 00 |.... |\n" assert o.b_op.inspect() == "00000000 e6 07 00 |... |\n" From 7b0b90ce73b305a8328aad10fa1c0cb86971639c Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 15:40:27 +0100 Subject: [PATCH 75/78] perform 32bit test using Python 3.7 --- docker/i386/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/i386/Dockerfile b/docker/i386/Dockerfile index 9037dda..356a863 100644 --- a/docker/i386/Dockerfile +++ b/docker/i386/Dockerfile @@ -2,13 +2,13 @@ FROM i386/ubuntu RUN apt-get update && \ apt-get -y install \ - python3.6 \ - python3.6-dev \ - python3.6-distutils \ + python3.7 \ + python3.7-dev \ + python3.7-distutils \ curl \ make && \ rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://bootstrap.pypa.io/pip/3.6/get-pip.py -o get-pip.py && \ - python3.6 get-pip.py +RUN curl -sSL https://bootstrap.pypa.io/pip/3.7/get-pip.py -o get-pip.py && \ + python3.7 get-pip.py RUN pip install pytest WORKDIR /app From b5233e2686964a60437784096f613c8a393bfd76 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 15:47:04 +0100 Subject: [PATCH 76/78] use pyproject.toml instead of setup.cfg --- .github/workflows/release.yml | 73 +- .github/workflows/tests.yml | 11 +- .gitignore | 1 + .python-version | 1 + Makefile | 53 +- pyproject.toml | 70 +- requirements-dev.txt | 12 - requirements.txt | 0 setup.cfg | 63 - setup.py | 5 - uv.lock | 2939 +++++++++++++++++++++++++++++++++ 11 files changed, 3074 insertions(+), 154 deletions(-) create mode 100644 .python-version delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 uv.lock diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b8f5e0..03e2376 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: tags: - v* +permissions: + contents: read + jobs: wait: @@ -25,13 +28,12 @@ jobs: if: steps.wait-for-tests.outputs.conclusion != 'success' build: - name: Build package runs-on: ubuntu-latest - needs: [wait] strategy: matrix: - python-version: ['3.12'] + python-version: + - "3.12" steps: - name: Checkout code @@ -42,50 +44,39 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Cache pip - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/requirements*.txt') }}-git-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/requirements*.txt') }} - ${{ runner.os }}-python-${{ matrix.python }}-pip- - ${{ runner.os }}-python - ${{ runner.os }}- - - - name: Upgrade pip and install dependencies + - name: Install uv run: | - python -m pip install --upgrade pip - python -m pip install --upgrade setuptools wheel - python -m pip install -r requirements.txt - python -m pip install -r requirements-dev.txt - - - name: Build package - run: python -m build + pip install uv - - name: Check package - run: twine check dist/* - - - name: Publish package - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + - name: Build release distributions run: | - twine upload --skip-existing dist/* + uv pip install --system build + python -m build - release: - name: Release version + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: release-dists + path: dist/ + + pypi-publish: runs-on: ubuntu-latest needs: [wait, build] + permissions: + id-token: write + + environment: + name: pypi + url: https://pypi.org/project/otp-mcp-server/ steps: - - name: Create release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Retrieve release distributions + uses: actions/download-artifact@v4 + with: + name: release-dists + path: dist/ + + - name: Publish release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - draft: false - prerelease: false + packages-dir: dist/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20a2332..b902c63 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,6 @@ jobs: strategy: matrix: python: - - "3.8" - "3.9" - "3.10" - "3.11" @@ -26,7 +25,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} @@ -34,9 +33,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/requirements*.txt') }}-git-${{ github.sha }} + key: ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/pyproject.toml') }}-git-${{ github.sha }} restore-keys: | - ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/requirements*.txt') }} + ${{ runner.os }}-python-${{ matrix.python }}-pip-${{ hashFiles('**/pyproject.toml') }} ${{ runner.os }}-python-${{ matrix.python }}-pip- ${{ runner.os }}-python-${{ matrix.python }}- ${{ runner.os }}-python @@ -45,9 +44,7 @@ jobs: - name: Upgrade pip and install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade setuptools wheel - python -m pip install -r requirements.txt - python -m pip install pytest + pip install -e .[test] - name: Run tests run: pytest diff --git a/.gitignore b/.gitignore index 553e4ea..e786812 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ nosetests.xml pyvenv.cfg site/ __pycache__ +.venv 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/Makefile b/Makefile index 4cfe394..3e523b0 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ SHELL=/bin/bash -e +VERSION := $(shell grep '__version__ = ' cstruct/__init__.py | sed 's/__version__ = "\(.*\)"/\1/') help: - @echo - make black ------ Format code - @echo - make isort ------ Sort imports + @echo - make ruff ------- Format code and sort imports @echo - make clean ------ Clean virtual environment @echo - make coverage --- Run tests coverage @echo - make docs ------- Make docs @@ -12,44 +12,49 @@ help: @echo - make typecheck -- Typecheck @echo - make venv ------- Create virtual environment -.PHONY: isort +.PHONY: codespell codespell: - @codespell -w cstruct tests examples setup.py + uv run codespell -w cstruct tests examples -.PHONY: isort -isort: - @isort --profile black cstruct tests examples setup.py - -.PHONY: black -black: isort - @black -S cstruct tests examples setup.py +.PHONY: ruff +ruff: + uv run ruff format cstruct tests examples +.PHONY: clean clean: - -rm -rf build dist - -rm -rf *.egg-info - -rm -rf bin lib lib64 share include pyvenv.cfg + -rm -rf build dist pyvenv.cfg *.egg-info .venv +.PHONY: coverage coverage: - @pytest --cov --cov-report=term-missing + uv run pytest --cov --cov-report=term-missing .PHONY: docs docs: - @mkdocs build - @mkdocs gh-deploy + uv run mkdocs build + uv run mkdocs gh-deploy +.PHONY: lint lint: - flake8 cstruct tests + uv run ruff check cstruct tests +.PHONY: test test: - pytest + uv run pytest +.PHONY: test-32bit test-32bit: - @make -C docker/i386 test + make -C docker/i386 test +.PHONY: typecheck typecheck: - mypy --strict --no-warn-unused-ignores cstruct + uv run mypy --strict --no-warn-unused-ignores cstruct + +.PHONY: tag +tag: + git tag v${VERSION} +.PHONY: venv venv: - python3 -m venv . || python3 -m virtualenv . - . bin/activate; pip install -Ur requirements.txt - . bin/activate; pip install -Ur requirements-dev.txt + uv venv + uv pip install -e . + uv pip install -e ".[dev]" diff --git a/pyproject.toml b/pyproject.toml index ccb95b4..15a5176 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,73 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" -[tool.black] +[project] +name = "cstruct" +dynamic = ["version"] +description = "C-style structs for Python" +readme = "README.md" +license = "MIT" +license-files = ["LICENSE"] +authors = [ + { name = "Andrea Bonomi", email = "andrea.bonomi@gmail.com" } +] +keywords = ["struct", "cstruct", "enum", "binary", "pack", "unpack"] +requires-python = ">=3.7" + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python :: 3", +] + +[project.urls] +Homepage = "http://github.com/andreax79/python-cstruct" +Source = "http://github.com/andreax79/python-cstruct" +Issues = "http://github.com/andreax79/python-cstruct/issues" +Documentation = "https://python-cstruct.readthedocs.io/en/latest/" + +[project.optional-dependencies] +dev = [ + "pytest", + "pytest-cov", + "codespell", + "coverage[toml]", + "ruff", + "markdown_include", + "mkdocs", + "mkdocs-material", + "mkdocstrings", + "mkdocstrings-python", + "mypy" +] +test = [ + "pytest", + "pytest-cov", + "coverage[toml]" +] + +[tool.setuptools] +zip-safe = true +include-package-data = true + +[tool.setuptools.dynamic] +version = { attr = "cstruct.__version__" } + +[tool.setuptools.packages.find] +include = ["cstruct*"] +exclude = ["ez_setup", "examples", "tests"] + +[tool.ruff] line-length = 132 +[tool.ruff.lint] +ignore = [ "E731", "E711" ] + [tool.coverage.run] source = ["cstruct"] @@ -13,3 +76,6 @@ exclude_lines = [ "# pragma: no cover", "if TYPE_CHECKING:" ] [tool.isort] profile = "black" + +[tool.black] +line-length = 132 diff --git a/requirements-dev.txt b/requirements-dev.txt index ea86fe9..752a6ce 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,16 +1,4 @@ -black -build -codespell -coverage[toml] -flake8 -isort markdown_include mkdocs mkdocs-material mkdocstrings[python] -mypy -pytest -pytest-cov -setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability -tox -twine<3.4 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e69de29..0000000 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index f737c08..0000000 --- a/setup.cfg +++ /dev/null @@ -1,63 +0,0 @@ -[metadata] -name = cstruct -version = attr: cstruct.__version__ -keywords = struct, cstruct, enum, binary, pack, unpack -description = C-style structs for Python -author = Andrea Bonomi -author_email = andrea.bonomi@gmail.com -url = http://github.com/andreax79/python-cstruct -long_description = file: README.md -long_description_content_type = text/markdown -license = MIT -license_files = LICENSE -platforms = any -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Console - Intended Audience :: Developers - License :: OSI Approved :: MIT License - Operating System :: OS Independent - Programming Language :: Python - Topic :: Software Development :: Libraries :: Python Modules - Programming Language :: Python :: 3.6 - 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 -project_urls = - Bug Tracker = http://github.com/andreax79/python-cstruct/issues - Documentation = https://python-cstruct.readthedocs.io/en/latest/ - Source Code = http://github.com/andreax79/python-cstruct - -[options] -zip_safe = True -include_package_data = True -python_requires = >=3.6 -packages = find: - -[options.packages.find] -include = cstruct* -exclude = - ez_setup - examples - tests - -[options.extras_require] -test = pytest - -[aliases] -test = pytest - -[bdist_wheel] -universal = 1 - -[flake8] -max-line-length = 132 -extend-ignore = - E203 - E401 - W504 - E221 diff --git a/setup.py b/setup.py deleted file mode 100644 index dbe9716..0000000 --- a/setup.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -import setuptools - -if __name__ == "__main__": - setuptools.setup() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..dd7cdaa --- /dev/null +++ b/uv.lock @@ -0,0 +1,2939 @@ +version = 1 +revision = 3 +requires-python = ">=3.7" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version < '3.8'", +] + +[[package]] +name = "astunparse" +version = "1.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version == '3.8.*'" }, + { name = "wheel", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290, upload-time = "2019-12-22T18:12:13.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732, upload-time = "2019-12-22T18:12:11.297Z" }, +] + +[[package]] +name = "babel" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "pytz", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/80/cfbe44a9085d112e983282ee7ca4c00429bc4d1ce86ee5f4e60259ddff7f/Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363", size = 10795622, upload-time = "2023-12-12T13:33:16.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/35/4196b21041e29a42dc4f05866d0c94fa26c9da88ce12c38c2265e42c82fb/Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287", size = 11034798, upload-time = "2023-12-12T13:33:13.288Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "pytz", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backrefs" +version = "5.7.post1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/df/30/903f35159c87ff1d92aa3fcf8cb52de97632a21e0ae43ed940f5d033e01a/backrefs-5.7.post1.tar.gz", hash = "sha256:8b0f83b770332ee2f1c8244f4e03c77d127a0fa529328e6a0e77fa25bee99678", size = 6582270, upload-time = "2024-06-16T18:38:20.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/bb/47fc255d1060dcfd55b460236380edd8ebfc5b2a42a0799ca90c9fc983e3/backrefs-5.7.post1-py310-none-any.whl", hash = "sha256:c5e3fd8fd185607a7cb1fefe878cfb09c34c0be3c18328f12c574245f1c0287e", size = 380429, upload-time = "2024-06-16T18:38:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/39ef491caef3abae945f5a5fd72830d3b596bfac0630508629283585e213/backrefs-5.7.post1-py311-none-any.whl", hash = "sha256:712ea7e494c5bf3291156e28954dd96d04dc44681d0e5c030adf2623d5606d51", size = 392234, upload-time = "2024-06-16T18:38:12.283Z" }, + { url = "https://files.pythonhosted.org/packages/6a/00/33403f581b732ca70fdebab558e8bbb426a29c34e0c3ed674a479b74beea/backrefs-5.7.post1-py312-none-any.whl", hash = "sha256:a6142201c8293e75bce7577ac29e1a9438c12e730d73a59efdd1b75528d1a6c5", size = 398110, upload-time = "2024-06-16T18:38:14.257Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ea/df0ac74a26838f6588aa012d5d801831448b87d0a7d0aefbbfabbe894870/backrefs-5.7.post1-py38-none-any.whl", hash = "sha256:ec61b1ee0a4bfa24267f6b67d0f8c5ffdc8e0d7dc2f18a2685fd1d8d9187054a", size = 369477, upload-time = "2024-06-16T18:38:16.196Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e8/e43f535c0a17a695e5768670fc855a0e5d52dc0d4135b3915bfa355f65ac/backrefs-5.7.post1-py39-none-any.whl", hash = "sha256:05c04af2bf752bb9a6c9dcebb2aff2fab372d3d9d311f2a138540e307756bd3a", size = 380429, upload-time = "2024-06-16T18:38:18.079Z" }, +] + +[[package]] +name = "backrefs" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, + { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, +] + +[[package]] +name = "cached-property" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/2c/d21c1c23c2895c091fa7a91a54b6872098fea913526932d21902088a7c41/cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", size = 12244, upload-time = "2020-09-21T18:39:27.069Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/19/f2090f7dad41e225c7f2326e4cfe6fff49e57dedb5b53636c9551f86b069/cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0", size = 7573, upload-time = "2020-09-21T18:39:25.338Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4e/3926a1c11f0433791985727965263f788af00db3482d89a7545ca5ecc921/charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", size = 198599, upload-time = "2025-10-14T04:41:53.213Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7c/b92d1d1dcffc34592e71ea19c882b6709e43d20fa498042dea8b815638d7/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", size = 143090, upload-time = "2025-10-14T04:41:54.385Z" }, + { url = "https://files.pythonhosted.org/packages/84/ce/61a28d3bb77281eb24107b937a497f3c43089326d27832a63dcedaab0478/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", size = 139490, upload-time = "2025-10-14T04:41:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bd/c9e59a91b2061c6f8bb98a150670cb16d4cd7c4ba7d11ad0cdf789155f41/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", size = 155334, upload-time = "2025-10-14T04:41:56.724Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/f17ae176a80f22ff823456af91ba3bc59df308154ff53aef0d39eb3d3419/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", size = 152823, upload-time = "2025-10-14T04:41:58.236Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fa/cf5bb2409a385f78750e78c8d2e24780964976acdaaed65dbd6083ae5b40/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", size = 147618, upload-time = "2025-10-14T04:41:59.409Z" }, + { url = "https://files.pythonhosted.org/packages/9b/63/579784a65bc7de2d4518d40bb8f1870900163e86f17f21fd1384318c459d/charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", size = 145516, upload-time = "2025-10-14T04:42:00.579Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a9/94ec6266cd394e8f93a4d69cca651d61bf6ac58d2a0422163b30c698f2c7/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", size = 145266, upload-time = "2025-10-14T04:42:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/d6626eb97764b58c2779fa7928fa7d1a49adb8ce687c2dbba4db003c1939/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", size = 139559, upload-time = "2025-10-14T04:42:02.902Z" }, + { url = "https://files.pythonhosted.org/packages/09/01/ddbe6b01313ba191dbb0a43c7563bc770f2448c18127f9ea4b119c44dff0/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", size = 156653, upload-time = "2025-10-14T04:42:04.005Z" }, + { url = "https://files.pythonhosted.org/packages/95/c8/d05543378bea89296e9af4510b44c704626e191da447235c8fdedfc5b7b2/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", size = 145644, upload-time = "2025-10-14T04:42:05.211Z" }, + { url = "https://files.pythonhosted.org/packages/72/01/2866c4377998ef8a1f6802f6431e774a4c8ebe75b0a6e569ceec55c9cbfb/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", size = 153964, upload-time = "2025-10-14T04:42:06.341Z" }, + { url = "https://files.pythonhosted.org/packages/4a/66/66c72468a737b4cbd7851ba2c522fe35c600575fbeac944460b4fd4a06fe/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", size = 148777, upload-time = "2025-10-14T04:42:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/d0d56677fdddbffa8ca00ec411f67bb8c947f9876374ddc9d160d4f2c4b3/charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", size = 98687, upload-time = "2025-10-14T04:42:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/00/64/c3bc303d1b586480b1c8e6e1e2191a6d6dd40255244e5cf16763dcec52e6/charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", size = 106115, upload-time = "2025-10-14T04:42:09.793Z" }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version < '3.8'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "codespell" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/81/30/e1b32067c551d745df2bdc9f1d510422d8a5819ca3b610b4433654cf578c/codespell-2.2.5.tar.gz", hash = "sha256:6d9faddf6eedb692bf80c9a94ec13ab4f5fb585aabae5f3750727148d7b5be56", size = 242918, upload-time = "2023-06-14T18:00:23.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/bc/4bd1cdb7cf940ab8e8e619d3ad24c88b0257b030c6b0dd64cba3fdfa7bb8/codespell-2.2.5-py3-none-any.whl", hash = "sha256:efa037f54b73c84f7bd14ce8e853d5f822cdd6386ef0ff32e957a3919435b9ec", size = 242673, upload-time = "2023-06-14T18:00:21.806Z" }, +] + +[[package]] +name = "codespell" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/15/e0/709453393c0ea77d007d907dd436b3ee262e28b30995ea1aa36c6ffbccaf/codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5", size = 344740, upload-time = "2025-01-28T18:52:39.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425", size = 344501, upload-time = "2025-01-28T18:52:37.057Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.2.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575, upload-time = "2023-05-29T20:08:50.273Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", size = 200724, upload-time = "2023-05-29T20:07:03.422Z" }, + { url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", size = 201024, upload-time = "2023-05-29T20:07:05.694Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", size = 229528, upload-time = "2023-05-29T20:07:07.307Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", size = 227842, upload-time = "2023-05-29T20:07:09.331Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", size = 228717, upload-time = "2023-05-29T20:07:11.38Z" }, + { url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", size = 234632, upload-time = "2023-05-29T20:07:13.376Z" }, + { url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", size = 232875, upload-time = "2023-05-29T20:07:15.093Z" }, + { url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", size = 234094, upload-time = "2023-05-29T20:07:17.013Z" }, + { url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", size = 203184, upload-time = "2023-05-29T20:07:18.69Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", size = 204096, upload-time = "2023-05-29T20:07:20.153Z" }, + { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895, upload-time = "2023-05-29T20:07:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120, upload-time = "2023-05-29T20:07:23.765Z" }, + { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178, upload-time = "2023-05-29T20:07:25.281Z" }, + { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754, upload-time = "2023-05-29T20:07:27.044Z" }, + { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558, upload-time = "2023-05-29T20:07:28.743Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509, upload-time = "2023-05-29T20:07:30.434Z" }, + { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924, upload-time = "2023-05-29T20:07:32.065Z" }, + { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977, upload-time = "2023-05-29T20:07:34.184Z" }, + { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168, upload-time = "2023-05-29T20:07:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185, upload-time = "2023-05-29T20:07:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020, upload-time = "2023-05-29T20:07:38.724Z" }, + { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994, upload-time = "2023-05-29T20:07:40.274Z" }, + { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358, upload-time = "2023-05-29T20:07:41.998Z" }, + { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316, upload-time = "2023-05-29T20:07:43.539Z" }, + { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159, upload-time = "2023-05-29T20:07:44.982Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127, upload-time = "2023-05-29T20:07:46.522Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833, upload-time = "2023-05-29T20:07:47.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463, upload-time = "2023-05-29T20:07:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347, upload-time = "2023-05-29T20:07:51.909Z" }, + { url = "https://files.pythonhosted.org/packages/80/d7/67937c80b8fd4c909fdac29292bc8b35d9505312cff6bcab41c53c5b1df6/coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f", size = 200580, upload-time = "2023-05-29T20:07:54.076Z" }, + { url = "https://files.pythonhosted.org/packages/7a/05/084864fa4bbf8106f44fb72a56e67e0cd372d3bf9d893be818338c81af5d/coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb", size = 226237, upload-time = "2023-05-29T20:07:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/67/a2/6fa66a50e6e894286d79a3564f42bd54a9bd27049dc0a63b26d9924f0aa3/coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9", size = 224256, upload-time = "2023-05-29T20:07:58.189Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c0/73f139794c742840b9ab88e2e17fe14a3d4668a166ff95d812ac66c0829d/coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd", size = 225550, upload-time = "2023-05-29T20:08:00.383Z" }, + { url = "https://files.pythonhosted.org/packages/03/ec/6f30b4e0c96ce03b0e64aec46b4af2a8c49b70d1b5d0d69577add757b946/coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a", size = 232440, upload-time = "2023-05-29T20:08:02.495Z" }, + { url = "https://files.pythonhosted.org/packages/22/c1/2f6c1b6f01a0996c9e067a9c780e1824351dbe17faae54388a4477e6d86f/coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959", size = 230897, upload-time = "2023-05-29T20:08:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d6/53e999ec1bf7498ca4bc5f3b8227eb61db39068d2de5dcc359dec5601b5a/coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02", size = 232024, upload-time = "2023-05-29T20:08:06.031Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/383305500d24122dbed73e505a4d6828f8f3356d1f68ab6d32c781754b81/coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f", size = 203293, upload-time = "2023-05-29T20:08:07.598Z" }, + { url = "https://files.pythonhosted.org/packages/0e/bc/7e3a31534fabb043269f14fb64e2bb2733f85d4cf39e5bbc71357c57553a/coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0", size = 204040, upload-time = "2023-05-29T20:08:09.919Z" }, + { url = "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5", size = 200689, upload-time = "2023-05-29T20:08:11.594Z" }, + { url = "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5", size = 200986, upload-time = "2023-05-29T20:08:13.228Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9", size = 230648, upload-time = "2023-05-29T20:08:15.11Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e3/f552d5871943f747165b92a924055c5d6daa164ae659a13f9018e22f3990/coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6", size = 228511, upload-time = "2023-05-29T20:08:16.877Z" }, + { url = "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e", size = 229852, upload-time = "2023-05-29T20:08:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/0d/31/340428c238eb506feb96d4fb5c9ea614db1149517f22cc7ab8c6035ef6d9/coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050", size = 235578, upload-time = "2023-05-29T20:08:20.298Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ce/97c1dd6592c908425622fe7f31c017d11cf0421729b09101d4de75bcadc8/coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5", size = 234079, upload-time = "2023-05-29T20:08:22.365Z" }, + { url = "https://files.pythonhosted.org/packages/de/a3/5a98dc9e239d0dc5f243ef5053d5b1bdcaa1dee27a691dfc12befeccf878/coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f", size = 234991, upload-time = "2023-05-29T20:08:24.974Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fb/78986d3022e5ccf2d4370bc43a5fef8374f092b3c21d32499dee8e30b7b6/coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e", size = 203160, upload-time = "2023-05-29T20:08:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/6b3c9c363fb1433c79128e0d692863deb761b1b78162494abb9e5c328bc0/coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c", size = 204085, upload-time = "2023-05-29T20:08:28.146Z" }, + { url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", size = 200725, upload-time = "2023-05-29T20:08:29.851Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", size = 201022, upload-time = "2023-05-29T20:08:31.429Z" }, + { url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", size = 229102, upload-time = "2023-05-29T20:08:32.982Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", size = 227441, upload-time = "2023-05-29T20:08:35.044Z" }, + { url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", size = 228265, upload-time = "2023-05-29T20:08:36.861Z" }, + { url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", size = 234217, upload-time = "2023-05-29T20:08:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", size = 232466, upload-time = "2023-05-29T20:08:40.768Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", size = 233669, upload-time = "2023-05-29T20:08:42.944Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", size = 203199, upload-time = "2023-05-29T20:08:44.734Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", size = 204109, upload-time = "2023-05-29T20:08:46.417Z" }, + { url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", size = 193207, upload-time = "2023-05-29T20:08:48.153Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +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, upload-time = "2024-08-04T19:43:07.695Z" }, + { 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, upload-time = "2024-08-04T19:43:10.15Z" }, + { 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, upload-time = "2024-08-04T19:43:12.405Z" }, + { 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, upload-time = "2024-08-04T19:43:14.078Z" }, + { 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, upload-time = "2024-08-04T19:43:16.632Z" }, + { 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, upload-time = "2024-08-04T19:43:19.049Z" }, + { 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, upload-time = "2024-08-04T19:43:21.246Z" }, + { 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, upload-time = "2024-08-04T19:43:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, + { 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, upload-time = "2024-08-04T19:43:29.115Z" }, + { 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, upload-time = "2024-08-04T19:43:31.285Z" }, + { 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, upload-time = "2024-08-04T19:43:33.581Z" }, + { 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, upload-time = "2024-08-04T19:43:35.301Z" }, + { 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, upload-time = "2024-08-04T19:43:37.578Z" }, + { 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, upload-time = "2024-08-04T19:43:39.92Z" }, + { 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, upload-time = "2024-08-04T19:43:41.453Z" }, + { 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, upload-time = "2024-08-04T19:43:43.037Z" }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, + { 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, upload-time = "2024-08-04T19:43:49.082Z" }, + { 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, upload-time = "2024-08-04T19:43:52.15Z" }, + { 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, upload-time = "2024-08-04T19:43:53.746Z" }, + { 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, upload-time = "2024-08-04T19:43:55.993Z" }, + { 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, upload-time = "2024-08-04T19:43:57.618Z" }, + { 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, upload-time = "2024-08-04T19:44:00.012Z" }, + { 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, upload-time = "2024-08-04T19:44:01.713Z" }, + { 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, upload-time = "2024-08-04T19:44:03.898Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, + { 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, upload-time = "2024-08-04T19:44:09.453Z" }, + { 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, upload-time = "2024-08-04T19:44:11.045Z" }, + { 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, upload-time = "2024-08-04T19:44:12.83Z" }, + { 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, upload-time = "2024-08-04T19:44:15.393Z" }, + { 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, upload-time = "2024-08-04T19:44:17.466Z" }, + { 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, upload-time = "2024-08-04T19:44:19.336Z" }, + { 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, upload-time = "2024-08-04T19:44:20.994Z" }, + { 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, upload-time = "2024-08-04T19:44:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, + { 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, upload-time = "2024-08-04T19:44:29.028Z" }, + { 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, upload-time = "2024-08-04T19:44:30.673Z" }, + { 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, upload-time = "2024-08-04T19:44:32.412Z" }, + { 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, upload-time = "2024-08-04T19:44:34.547Z" }, + { 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, upload-time = "2024-08-04T19:44:36.313Z" }, + { 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, upload-time = "2024-08-04T19:44:38.155Z" }, + { 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, upload-time = "2024-08-04T19:44:39.883Z" }, + { 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, upload-time = "2024-08-04T19:44:41.59Z" }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, + { 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, upload-time = "2024-08-04T19:44:47.694Z" }, + { 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, upload-time = "2024-08-04T19:44:49.32Z" }, + { 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, upload-time = "2024-08-04T19:44:51.631Z" }, + { 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, upload-time = "2024-08-04T19:44:53.464Z" }, + { 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, upload-time = "2024-08-04T19:44:55.165Z" }, + { 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, upload-time = "2024-08-04T19:44:57.269Z" }, + { 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, upload-time = "2024-08-04T19:44:59.033Z" }, + { 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, upload-time = "2024-08-04T19:45:01.398Z" }, + { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, + { 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, upload-time = "2024-08-04T19:45:08.358Z" }, + { 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, upload-time = "2024-08-04T19:45:11.526Z" }, + { 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, upload-time = "2024-08-04T19:45:13.202Z" }, + { 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, upload-time = "2024-08-04T19:45:14.961Z" }, + { 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, upload-time = "2024-08-04T19:45:16.924Z" }, + { 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, upload-time = "2024-08-04T19:45:18.672Z" }, + { 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, upload-time = "2024-08-04T19:45:20.63Z" }, + { 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, upload-time = "2024-08-04T19:45:23.062Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] + +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] + +[[package]] +name = "coverage" +version = "7.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" }, + { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" }, + { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" }, + { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" }, + { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" }, + { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" }, + { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" }, + { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, + { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, + { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, + { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, + { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, + { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, + { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, + { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, + { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, + { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, + { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, + { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, + { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, + { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, + { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, + { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, + { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "cstruct" +source = { editable = "." } + +[package.optional-dependencies] +dev = [ + { name = "codespell", version = "2.2.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "codespell", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.8'" }, + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.8.*'" }, + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "markdown-include" }, + { name = "mkdocs", version = "1.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "mkdocs-material", version = "9.2.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs-material", version = "9.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "mkdocstrings", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocstrings", version = "0.26.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mkdocstrings", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings-python", version = "1.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocstrings-python", version = "1.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocstrings-python", version = "1.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mkdocstrings-python", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy", version = "1.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mypy", version = "1.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-cov", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "ruff" }, +] +test = [ + { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.8'" }, + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.8.*'" }, + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-cov", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] + +[package.metadata] +requires-dist = [ + { name = "codespell", marker = "extra == 'dev'" }, + { name = "coverage", extras = ["toml"], marker = "extra == 'dev'" }, + { name = "coverage", extras = ["toml"], marker = "extra == 'test'" }, + { name = "markdown-include", marker = "extra == 'dev'" }, + { name = "mkdocs", marker = "extra == 'dev'" }, + { name = "mkdocs-material", marker = "extra == 'dev'" }, + { name = "mkdocstrings", marker = "extra == 'dev'" }, + { name = "mkdocstrings-python", marker = "extra == 'dev'" }, + { name = "mypy", marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-cov", marker = "extra == 'dev'" }, + { name = "pytest-cov", marker = "extra == 'test'" }, + { name = "ruff", marker = "extra == 'dev'" }, +] +provides-extras = ["dev", "test"] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +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 = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "griffe" +version = "0.30.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "cached-property", marker = "python_full_version < '3.8'" }, + { name = "colorama", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/1e/57a435627c00c2e122a9404c4f76b4ef0cd19b9cf69f806b6db9a372f9a5/griffe-0.30.1.tar.gz", hash = "sha256:007cc11acd20becf1bb8f826419a52b9d403bbad9d8c8535699f5440ddc0a109", size = 110164, upload-time = "2023-07-02T15:56:32.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/3e/3ea68eddeab546a0c02a3d5c1b4e9440d45fe83027088665a2b189d838fc/griffe-0.30.1-py3-none-any.whl", hash = "sha256:b2f3df6952995a6bebe19f797189d67aba7c860755d3d21cc80f64d076d0154c", size = 97343, upload-time = "2023-07-02T15:56:29.948Z" }, +] + +[[package]] +name = "griffe" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "astunparse", marker = "python_full_version == '3.8.*'" }, + { name = "colorama", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e9/b2c86ad9d69053e497a24ceb25d661094fb321ab4ed39a8b71793dcbae82/griffe-1.4.0.tar.gz", hash = "sha256:8fccc585896d13f1221035d32c50dec65830c87d23f9adb9b1e6f3d63574f7f5", size = 381028, upload-time = "2024-10-11T12:53:54.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/7c/e9e66869c2e4c9b378474e49c993128ec0131ef4721038b6d06e50538caf/griffe-1.4.0-py3-none-any.whl", hash = "sha256:e589de8b8c137e99a46ec45f9598fc0ac5b6868ce824b24db09c02d117b89bc5", size = 127015, upload-time = "2024-10-11T12:53:52.383Z" }, +] + +[[package]] +name = "griffe" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, +] + +[[package]] +name = "griffe" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "zipp", version = "3.15.0", source = { registry = "https://pypi.org/simple" }, 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 = "importlib-metadata" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "zipp", version = "3.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +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 = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "librt" +version = "0.7.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/29/47f29026ca17f35cf299290292d5f8331f5077364974b7675a353179afa2/librt-0.7.7.tar.gz", hash = "sha256:81d957b069fed1890953c3b9c3895c7689960f233eea9a1d9607f71ce7f00b2c", size = 145910, upload-time = "2026-01-01T23:52:22.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/84/2cfb1f3b9b60bab52e16a220c931223fc8e963d0d7bb9132bef012aafc3f/librt-0.7.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4836c5645f40fbdc275e5670819bde5ab5f2e882290d304e3c6ddab1576a6d0", size = 54709, upload-time = "2026-01-01T23:50:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/19/a1/3127b277e9d3784a8040a54e8396d9ae5c64d6684dc6db4b4089b0eedcfb/librt-0.7.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae8aec43117a645a31e5f60e9e3a0797492e747823b9bda6972d521b436b4e8", size = 56658, upload-time = "2026-01-01T23:50:49.74Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e9/b91b093a5c42eb218120445f3fef82e0b977fa2225f4d6fc133d25cdf86a/librt-0.7.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:aea05f701ccd2a76b34f0daf47ca5068176ff553510b614770c90d76ac88df06", size = 161026, upload-time = "2026-01-01T23:50:50.853Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cb/1ded77d5976a79d7057af4a010d577ce4f473ff280984e68f4974a3281e5/librt-0.7.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b16ccaeff0ed4355dfb76fe1ea7a5d6d03b5ad27f295f77ee0557bc20a72495", size = 169529, upload-time = "2026-01-01T23:50:52.24Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/6ca5bdaa701e15f05000ac1a4c5d1475c422d3484bd3d1ca9e8c2f5be167/librt-0.7.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c48c7e150c095d5e3cea7452347ba26094be905d6099d24f9319a8b475fcd3e0", size = 183271, upload-time = "2026-01-01T23:50:55.287Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2d/55c0e38073997b4bbb5ddff25b6d1bbba8c2f76f50afe5bb9c844b702f34/librt-0.7.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4dcee2f921a8632636d1c37f1bbdb8841d15666d119aa61e5399c5268e7ce02e", size = 179039, upload-time = "2026-01-01T23:50:56.807Z" }, + { url = "https://files.pythonhosted.org/packages/33/4e/3662a41ae8bb81b226f3968426293517b271d34d4e9fd4b59fc511f1ae40/librt-0.7.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14ef0f4ac3728ffd85bfc58e2f2f48fb4ef4fa871876f13a73a7381d10a9f77c", size = 173505, upload-time = "2026-01-01T23:50:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5d/cf768deb8bdcbac5f8c21fcb32dd483d038d88c529fd351bbe50590b945d/librt-0.7.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4ab69fa37f8090f2d971a5d2bc606c7401170dbdae083c393d6cbf439cb45b8", size = 193570, upload-time = "2026-01-01T23:50:59.546Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/ee70effd13f1d651976d83a2812391f6203971740705e3c0900db75d4bce/librt-0.7.7-cp310-cp310-win32.whl", hash = "sha256:4bf3cc46d553693382d2abf5f5bd493d71bb0f50a7c0beab18aa13a5545c8900", size = 42600, upload-time = "2026-01-01T23:51:00.694Z" }, + { url = "https://files.pythonhosted.org/packages/f0/eb/dc098730f281cba76c279b71783f5de2edcba3b880c1ab84a093ef826062/librt-0.7.7-cp310-cp310-win_amd64.whl", hash = "sha256:f0c8fe5aeadd8a0e5b0598f8a6ee3533135ca50fd3f20f130f9d72baf5c6ac58", size = 48977, upload-time = "2026-01-01T23:51:01.726Z" }, + { url = "https://files.pythonhosted.org/packages/f0/56/30b5c342518005546df78841cb0820ae85a17e7d07d521c10ef367306d0d/librt-0.7.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a487b71fbf8a9edb72a8c7a456dda0184642d99cd007bc819c0b7ab93676a8ee", size = 54709, upload-time = "2026-01-01T23:51:02.774Z" }, + { url = "https://files.pythonhosted.org/packages/72/78/9f120e3920b22504d4f3835e28b55acc2cc47c9586d2e1b6ba04c3c1bf01/librt-0.7.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4d4efb218264ecf0f8516196c9e2d1a0679d9fb3bb15df1155a35220062eba8", size = 56663, upload-time = "2026-01-01T23:51:03.838Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/7d7a1ee7dfc1151836028eba25629afcf45b56bbc721293e41aa2e9b8934/librt-0.7.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b8bb331aad734b059c4b450cd0a225652f16889e286b2345af5e2c3c625c3d85", size = 161705, upload-time = "2026-01-01T23:51:04.917Z" }, + { url = "https://files.pythonhosted.org/packages/45/a5/952bc840ac8917fbcefd6bc5f51ad02b89721729814f3e2bfcc1337a76d6/librt-0.7.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:467dbd7443bda08338fc8ad701ed38cef48194017554f4c798b0a237904b3f99", size = 171029, upload-time = "2026-01-01T23:51:06.09Z" }, + { url = "https://files.pythonhosted.org/packages/fa/bf/c017ff7da82dc9192cf40d5e802a48a25d00e7639b6465cfdcee5893a22c/librt-0.7.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50d1d1ee813d2d1a3baf2873634ba506b263032418d16287c92ec1cc9c1a00cb", size = 184704, upload-time = "2026-01-01T23:51:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/77/ec/72f3dd39d2cdfd6402ab10836dc9cbf854d145226062a185b419c4f1624a/librt-0.7.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e5070cf3ec92d98f57574da0224f8c73faf1ddd6d8afa0b8c9f6e86997bc74", size = 180719, upload-time = "2026-01-01T23:51:09.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/86/06e7a1a81b246f3313bf515dd9613a1c81583e6fd7843a9f4d625c4e926d/librt-0.7.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bdb9f3d865b2dafe7f9ad7f30ef563c80d0ddd2fdc8cc9b8e4f242f475e34d75", size = 174537, upload-time = "2026-01-01T23:51:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/83/08/f9fb2edc9c7a76e95b2924ce81d545673f5b034e8c5dd92159d1c7dae0c6/librt-0.7.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8185c8497d45164e256376f9da5aed2bb26ff636c798c9dabe313b90e9f25b28", size = 195238, upload-time = "2026-01-01T23:51:11.762Z" }, + { url = "https://files.pythonhosted.org/packages/ba/56/ea2d2489d3ea1f47b301120e03a099e22de7b32c93df9a211e6ff4f9bf38/librt-0.7.7-cp311-cp311-win32.whl", hash = "sha256:44d63ce643f34a903f09ff7ca355aae019a3730c7afd6a3c037d569beeb5d151", size = 42939, upload-time = "2026-01-01T23:51:13.192Z" }, + { url = "https://files.pythonhosted.org/packages/58/7b/c288f417e42ba2a037f1c0753219e277b33090ed4f72f292fb6fe175db4c/librt-0.7.7-cp311-cp311-win_amd64.whl", hash = "sha256:7d13cc340b3b82134f8038a2bfe7137093693dcad8ba5773da18f95ad6b77a8a", size = 49240, upload-time = "2026-01-01T23:51:14.264Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/738eb33a6c1516fdb2dfd2a35db6e5300f7616679b573585be0409bc6890/librt-0.7.7-cp311-cp311-win_arm64.whl", hash = "sha256:983de36b5a83fe9222f4f7dcd071f9b1ac6f3f17c0af0238dadfb8229588f890", size = 42613, upload-time = "2026-01-01T23:51:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/1cd9d752070011641e8aee046c851912d5f196ecd726fffa7aed2070f3e0/librt-0.7.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a85a1fc4ed11ea0eb0a632459ce004a2d14afc085a50ae3463cd3dfe1ce43fc", size = 55687, upload-time = "2026-01-01T23:51:16.291Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/d5a1d4221c4fe7e76ae1459d24d6037783cb83c7645164c07d7daf1576ec/librt-0.7.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c87654e29a35938baead1c4559858f346f4a2a7588574a14d784f300ffba0efd", size = 57136, upload-time = "2026-01-01T23:51:17.363Z" }, + { url = "https://files.pythonhosted.org/packages/23/6f/0c86b5cb5e7ef63208c8cc22534df10ecc5278efc0d47fb8815577f3ca2f/librt-0.7.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c9faaebb1c6212c20afd8043cd6ed9de0a47d77f91a6b5b48f4e46ed470703fe", size = 165320, upload-time = "2026-01-01T23:51:18.455Z" }, + { url = "https://files.pythonhosted.org/packages/16/37/df4652690c29f645ffe405b58285a4109e9fe855c5bb56e817e3e75840b3/librt-0.7.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1908c3e5a5ef86b23391448b47759298f87f997c3bd153a770828f58c2bb4630", size = 174216, upload-time = "2026-01-01T23:51:19.599Z" }, + { url = "https://files.pythonhosted.org/packages/9a/d6/d3afe071910a43133ec9c0f3e4ce99ee6df0d4e44e4bddf4b9e1c6ed41cc/librt-0.7.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbc4900e95a98fc0729523be9d93a8fedebb026f32ed9ffc08acd82e3e181503", size = 189005, upload-time = "2026-01-01T23:51:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/d5/18/74060a870fe2d9fd9f47824eba6717ce7ce03124a0d1e85498e0e7efc1b2/librt-0.7.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7ea4e1fbd253e5c68ea0fe63d08577f9d288a73f17d82f652ebc61fa48d878d", size = 183961, upload-time = "2026-01-01T23:51:22.493Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5e/918a86c66304af66a3c1d46d54df1b2d0b8894babc42a14fb6f25511497f/librt-0.7.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef7699b7a5a244b1119f85c5bbc13f152cd38240cbb2baa19b769433bae98e50", size = 177610, upload-time = "2026-01-01T23:51:23.874Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d7/b5e58dc2d570f162e99201b8c0151acf40a03a39c32ab824dd4febf12736/librt-0.7.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:955c62571de0b181d9e9e0a0303c8bc90d47670a5eff54cf71bf5da61d1899cf", size = 199272, upload-time = "2026-01-01T23:51:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/87/8202c9bd0968bdddc188ec3811985f47f58ed161b3749299f2c0dd0f63fb/librt-0.7.7-cp312-cp312-win32.whl", hash = "sha256:1bcd79be209313b270b0e1a51c67ae1af28adad0e0c7e84c3ad4b5cb57aaa75b", size = 43189, upload-time = "2026-01-01T23:51:26.799Z" }, + { url = "https://files.pythonhosted.org/packages/61/8d/80244b267b585e7aa79ffdac19f66c4861effc3a24598e77909ecdd0850e/librt-0.7.7-cp312-cp312-win_amd64.whl", hash = "sha256:4353ee891a1834567e0302d4bd5e60f531912179578c36f3d0430f8c5e16b456", size = 49462, upload-time = "2026-01-01T23:51:27.813Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1f/75db802d6a4992d95e8a889682601af9b49d5a13bbfa246d414eede1b56c/librt-0.7.7-cp312-cp312-win_arm64.whl", hash = "sha256:a76f1d679beccccdf8c1958e732a1dfcd6e749f8821ee59d7bec009ac308c029", size = 42828, upload-time = "2026-01-01T23:51:28.804Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5e/d979ccb0a81407ec47c14ea68fb217ff4315521730033e1dd9faa4f3e2c1/librt-0.7.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f4a0b0a3c86ba9193a8e23bb18f100d647bf192390ae195d84dfa0a10fb6244", size = 55746, upload-time = "2026-01-01T23:51:29.828Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/3b65861fb32f802c3783d6ac66fc5589564d07452a47a8cf9980d531cad3/librt-0.7.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5335890fea9f9e6c4fdf8683061b9ccdcbe47c6dc03ab8e9b68c10acf78be78d", size = 57174, upload-time = "2026-01-01T23:51:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/030b50614b29e443607220097ebaf438531ea218c7a9a3e21ea862a919cd/librt-0.7.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b4346b1225be26def3ccc6c965751c74868f0578cbcba293c8ae9168483d811", size = 165834, upload-time = "2026-01-01T23:51:32.278Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e1/bd8d1eacacb24be26a47f157719553bbd1b3fe812c30dddf121c0436fd0b/librt-0.7.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a10b8eebdaca6e9fdbaf88b5aefc0e324b763a5f40b1266532590d5afb268a4c", size = 174819, upload-time = "2026-01-01T23:51:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/91d6c3372acf54a019c1ad8da4c9ecf4fc27d039708880bf95f48dbe426a/librt-0.7.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:067be973d90d9e319e6eb4ee2a9b9307f0ecd648b8a9002fa237289a4a07a9e7", size = 189607, upload-time = "2026-01-01T23:51:34.604Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ac/44604d6d3886f791fbd1c6ae12d5a782a8f4aca927484731979f5e92c200/librt-0.7.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:23d2299ed007812cccc1ecef018db7d922733382561230de1f3954db28433977", size = 184586, upload-time = "2026-01-01T23:51:35.845Z" }, + { url = "https://files.pythonhosted.org/packages/5c/26/d8a6e4c17117b7f9b83301319d9a9de862ae56b133efb4bad8b3aa0808c9/librt-0.7.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b6f8ea465524aa4c7420c7cc4ca7d46fe00981de8debc67b1cc2e9957bb5b9d", size = 178251, upload-time = "2026-01-01T23:51:37.018Z" }, + { url = "https://files.pythonhosted.org/packages/99/ab/98d857e254376f8e2f668e807daccc1f445e4b4fc2f6f9c1cc08866b0227/librt-0.7.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8df32a99cc46eb0ee90afd9ada113ae2cafe7e8d673686cf03ec53e49635439", size = 199853, upload-time = "2026-01-01T23:51:38.195Z" }, + { url = "https://files.pythonhosted.org/packages/7c/55/4523210d6ae5134a5da959900be43ad8bab2e4206687b6620befddb5b5fd/librt-0.7.7-cp313-cp313-win32.whl", hash = "sha256:86f86b3b785487c7760247bcdac0b11aa8bf13245a13ed05206286135877564b", size = 43247, upload-time = "2026-01-01T23:51:39.629Z" }, + { url = "https://files.pythonhosted.org/packages/25/40/3ec0fed5e8e9297b1cf1a3836fb589d3de55f9930e3aba988d379e8ef67c/librt-0.7.7-cp313-cp313-win_amd64.whl", hash = "sha256:4862cb2c702b1f905c0503b72d9d4daf65a7fdf5a9e84560e563471e57a56949", size = 49419, upload-time = "2026-01-01T23:51:40.674Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7a/aab5f0fb122822e2acbc776addf8b9abfb4944a9056c00c393e46e543177/librt-0.7.7-cp313-cp313-win_arm64.whl", hash = "sha256:0996c83b1cb43c00e8c87835a284f9057bc647abd42b5871e5f941d30010c832", size = 42828, upload-time = "2026-01-01T23:51:41.731Z" }, + { url = "https://files.pythonhosted.org/packages/69/9c/228a5c1224bd23809a635490a162e9cbdc68d99f0eeb4a696f07886b8206/librt-0.7.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:23daa1ab0512bafdd677eb1bfc9611d8ffbe2e328895671e64cb34166bc1b8c8", size = 55188, upload-time = "2026-01-01T23:51:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c2/0e7c6067e2b32a156308205e5728f4ed6478c501947e9142f525afbc6bd2/librt-0.7.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:558a9e5a6f3cc1e20b3168fb1dc802d0d8fa40731f6e9932dcc52bbcfbd37111", size = 56895, upload-time = "2026-01-01T23:51:44.534Z" }, + { url = "https://files.pythonhosted.org/packages/0e/77/de50ff70c80855eb79d1d74035ef06f664dd073fb7fb9d9fb4429651b8eb/librt-0.7.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2567cb48dc03e5b246927ab35cbb343376e24501260a9b5e30b8e255dca0d1d2", size = 163724, upload-time = "2026-01-01T23:51:45.571Z" }, + { url = "https://files.pythonhosted.org/packages/6e/19/f8e4bf537899bdef9e0bb9f0e4b18912c2d0f858ad02091b6019864c9a6d/librt-0.7.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6066c638cdf85ff92fc6f932d2d73c93a0e03492cdfa8778e6d58c489a3d7259", size = 172470, upload-time = "2026-01-01T23:51:46.823Z" }, + { url = "https://files.pythonhosted.org/packages/42/4c/dcc575b69d99076768e8dd6141d9aecd4234cba7f0e09217937f52edb6ed/librt-0.7.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a609849aca463074c17de9cda173c276eb8fee9e441053529e7b9e249dc8b8ee", size = 186806, upload-time = "2026-01-01T23:51:48.009Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f8/4094a2b7816c88de81239a83ede6e87f1138477d7ee956c30f136009eb29/librt-0.7.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:add4e0a000858fe9bb39ed55f31085506a5c38363e6eb4a1e5943a10c2bfc3d1", size = 181809, upload-time = "2026-01-01T23:51:49.35Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ac/821b7c0ab1b5a6cd9aee7ace8309c91545a2607185101827f79122219a7e/librt-0.7.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a3bfe73a32bd0bdb9a87d586b05a23c0a1729205d79df66dee65bb2e40d671ba", size = 175597, upload-time = "2026-01-01T23:51:50.636Z" }, + { url = "https://files.pythonhosted.org/packages/71/f9/27f6bfbcc764805864c04211c6ed636fe1d58f57a7b68d1f4ae5ed74e0e0/librt-0.7.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0ecce0544d3db91a40f8b57ae26928c02130a997b540f908cefd4d279d6c5848", size = 196506, upload-time = "2026-01-01T23:51:52.535Z" }, + { url = "https://files.pythonhosted.org/packages/46/ba/c9b9c6fc931dd7ea856c573174ccaf48714905b1a7499904db2552e3bbaf/librt-0.7.7-cp314-cp314-win32.whl", hash = "sha256:8f7a74cf3a80f0c3b0ec75b0c650b2f0a894a2cec57ef75f6f72c1e82cdac61d", size = 39747, upload-time = "2026-01-01T23:51:53.683Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/cd1269337c4cde3ee70176ee611ab0058aa42fc8ce5c9dce55f48facfcd8/librt-0.7.7-cp314-cp314-win_amd64.whl", hash = "sha256:3d1fe2e8df3268dd6734dba33ededae72ad5c3a859b9577bc00b715759c5aaab", size = 45971, upload-time = "2026-01-01T23:51:54.697Z" }, + { url = "https://files.pythonhosted.org/packages/79/fd/e0844794423f5583108c5991313c15e2b400995f44f6ec6871f8aaf8243c/librt-0.7.7-cp314-cp314-win_arm64.whl", hash = "sha256:2987cf827011907d3dfd109f1be0d61e173d68b1270107bb0e89f2fca7f2ed6b", size = 39075, upload-time = "2026-01-01T23:51:55.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/02/211fd8f7c381e7b2a11d0fdfcd410f409e89967be2e705983f7c6342209a/librt-0.7.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8e92c8de62b40bfce91d5e12c6e8b15434da268979b1af1a6589463549d491e6", size = 57368, upload-time = "2026-01-01T23:51:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b6/aca257affae73ece26041ae76032153266d110453173f67d7603058e708c/librt-0.7.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f683dcd49e2494a7535e30f779aa1ad6e3732a019d80abe1309ea91ccd3230e3", size = 59238, upload-time = "2026-01-01T23:51:58.066Z" }, + { url = "https://files.pythonhosted.org/packages/96/47/7383a507d8e0c11c78ca34c9d36eab9000db5989d446a2f05dc40e76c64f/librt-0.7.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b15e5d17812d4d629ff576699954f74e2cc24a02a4fc401882dd94f81daba45", size = 183870, upload-time = "2026-01-01T23:51:59.204Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/50f3d8eec8efdaf79443963624175c92cec0ba84827a66b7fcfa78598e51/librt-0.7.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c084841b879c4d9b9fa34e5d5263994f21aea7fd9c6add29194dbb41a6210536", size = 194608, upload-time = "2026-01-01T23:52:00.419Z" }, + { url = "https://files.pythonhosted.org/packages/23/d9/1b6520793aadb59d891e3b98ee057a75de7f737e4a8b4b37fdbecb10d60f/librt-0.7.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c8fb9966f84737115513fecbaf257f9553d067a7dd45a69c2c7e5339e6a8dc", size = 206776, upload-time = "2026-01-01T23:52:01.705Z" }, + { url = "https://files.pythonhosted.org/packages/ff/db/331edc3bba929d2756fa335bfcf736f36eff4efcb4f2600b545a35c2ae58/librt-0.7.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5fb1ecb2c35362eab2dbd354fd1efa5a8440d3e73a68be11921042a0edc0ff", size = 203206, upload-time = "2026-01-01T23:52:03.315Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e1/6af79ec77204e85f6f2294fc171a30a91bb0e35d78493532ed680f5d98be/librt-0.7.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d1454899909d63cc9199a89fcc4f81bdd9004aef577d4ffc022e600c412d57f3", size = 196697, upload-time = "2026-01-01T23:52:04.857Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/de55ecce4b2796d6d243295c221082ca3a944dc2fb3a52dcc8660ce7727d/librt-0.7.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7ef28f2e7a016b29792fe0a2dd04dec75725b32a1264e390c366103f834a9c3a", size = 217193, upload-time = "2026-01-01T23:52:06.159Z" }, + { url = "https://files.pythonhosted.org/packages/41/61/33063e271949787a2f8dd33c5260357e3d512a114fc82ca7890b65a76e2d/librt-0.7.7-cp314-cp314t-win32.whl", hash = "sha256:5e419e0db70991b6ba037b70c1d5bbe92b20ddf82f31ad01d77a347ed9781398", size = 40277, upload-time = "2026-01-01T23:52:07.625Z" }, + { url = "https://files.pythonhosted.org/packages/06/21/1abd972349f83a696ea73159ac964e63e2d14086fdd9bc7ca878c25fced4/librt-0.7.7-cp314-cp314t-win_amd64.whl", hash = "sha256:d6b7d93657332c817b8d674ef6bf1ab7796b4f7ce05e420fd45bd258a72ac804", size = 46765, upload-time = "2026-01-01T23:52:08.647Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/b756c7708143a63fca65a51ca07990fa647db2cc8fcd65177b9e96680255/librt-0.7.7-cp314-cp314t-win_arm64.whl", hash = "sha256:142c2cd91794b79fd0ce113bd658993b7ede0fe93057668c2f98a45ca00b7e91", size = 39724, upload-time = "2026-01-01T23:52:09.745Z" }, + { url = "https://files.pythonhosted.org/packages/e2/34/b88347b7bac496c1433e2f9bf124b0024733654b1bb4bcbf6ccf24d83e2e/librt-0.7.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8ffe3431d98cc043a14e88b21288b5ec7ee12cb01260e94385887f285ef9389", size = 54841, upload-time = "2026-01-01T23:52:10.751Z" }, + { url = "https://files.pythonhosted.org/packages/01/fc/394ef13f4a9a407e43e76a8b0002042f53e22401014ee19544bab99ba2c9/librt-0.7.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e40d20ae1722d6b8ea6acf4597e789604649dcd9c295eb7361a28225bc2e9e12", size = 56804, upload-time = "2026-01-01T23:52:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/88/53/0d49f17dd11495f0274d34318bd5d1c1aa183ce97c45a2dce8fda9b650af/librt-0.7.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f2cb63c49bc96847c3bb8dca350970e4dcd19936f391cfdfd057dcb37c4fa97e", size = 159682, upload-time = "2026-01-01T23:52:13.34Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/cce20900af63bbc22abacb197622287cf210cfdf2da352131fa48c3e490e/librt-0.7.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2f8dcf5ab9f80fb970c6fd780b398efb2f50c1962485eb8d3ab07788595a48", size = 168512, upload-time = "2026-01-01T23:52:14.52Z" }, + { url = "https://files.pythonhosted.org/packages/18/aa/4d5e0e98b47998297ec58e14561346f38bc4ad2d7c4d100e0a3baead06e8/librt-0.7.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1f5cc41a570269d1be7a676655875e3a53de4992a9fa38efb7983e97cf73d7c", size = 182231, upload-time = "2026-01-01T23:52:15.656Z" }, + { url = "https://files.pythonhosted.org/packages/d7/76/6dbde6632fd959f4ffb1b9a6ee67ae096adce6222282c7b9cd131787ea16/librt-0.7.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff1fb2dfef035549565a4124998fadcb7a3d4957131ddf004a56edeb029626b3", size = 178268, upload-time = "2026-01-01T23:52:16.851Z" }, + { url = "https://files.pythonhosted.org/packages/83/7d/a3ce1a98fa5a79c87e8d24a6595ba5beff40f500051d933f771975b81df9/librt-0.7.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ab2a2a9cd7d044e1a11ca64a86ad3361d318176924bbe5152fbc69f99be20b8c", size = 172569, upload-time = "2026-01-01T23:52:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a7/01f6cbc77b0ccb22d9ad939ddcd1529a521d3e79c5b1eb3ed5b2c158e8dd/librt-0.7.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad3fc2d859a709baf9dd9607bb72f599b1cfb8a39eafd41307d0c3c4766763cb", size = 192746, upload-time = "2026-01-01T23:52:19.235Z" }, + { url = "https://files.pythonhosted.org/packages/5b/83/9da96065a4f5a44eb1b7e6611c729544b84bb5dd6806acbf0c82ba3e3c27/librt-0.7.7-cp39-cp39-win32.whl", hash = "sha256:f83c971eb9d2358b6a18da51dc0ae00556ac7c73104dde16e9e14c15aaf685ca", size = 42550, upload-time = "2026-01-01T23:52:20.392Z" }, + { url = "https://files.pythonhosted.org/packages/34/b3/85aef151a052a40521f5b54005908a22c437dd4c952800d5e5efce99a47d/librt-0.7.7-cp39-cp39-win_amd64.whl", hash = "sha256:264720fc288c86039c091a4ad63419a5d7cabbf1c1c9933336a957ed2483e570", size = 48957, upload-time = "2026-01-01T23:52:21.43Z" }, +] + +[[package]] +name = "markdown" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/2a/62841f4fb1fef5fa015ded48d02401cd95643ca03b6760b29437b62a04a4/Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6", size = 324459, upload-time = "2023-07-25T15:13:45.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/b5/228c1cdcfe138f1a8e01ab1b54284c8b83735476cb22b6ba251656ed13ad/Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941", size = 94174, upload-time = "2023-07-25T15:13:43.124Z" }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349, upload-time = "2024-08-16T15:55:16.176Z" }, +] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, +] + +[[package]] +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, +] + +[[package]] +name = "markdown-include" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/d8/66bf162fe6c1adb619f94a6da599323eecacf15b6d57469d0fd0421c10df/markdown-include-0.8.1.tar.gz", hash = "sha256:1d0623e0fc2757c38d35df53752768356162284259d259c486b4ab6285cdbbe3", size = 21873, upload-time = "2023-02-07T09:47:26.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/e2/c4d20b21a05fe0fee571649cebc05f7f72e80b1a743f932e7326125e6c9e/markdown_include-0.8.1-py3-none-any.whl", hash = "sha256:32f0635b9cfef46997b307e2430022852529f7a5b87c0075c504283e7cc7db53", size = 18837, upload-time = "2023-02-07T09:47:25.03Z" }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206, upload-time = "2024-02-02T16:30:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079, upload-time = "2024-02-02T16:30:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620, upload-time = "2024-02-02T16:30:08.31Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818, upload-time = "2024-02-02T16:30:09.577Z" }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493, upload-time = "2024-02-02T16:30:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630, upload-time = "2024-02-02T16:30:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745, upload-time = "2024-02-02T16:30:14.222Z" }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021, upload-time = "2024-02-02T16:30:16.032Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659, upload-time = "2024-02-02T16:30:17.079Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213, upload-time = "2024-02-02T16:30:18.251Z" }, + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" }, + { url = "https://files.pythonhosted.org/packages/a7/88/a940e11827ea1c136a34eca862486178294ae841164475b9ab216b80eb8e/MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", size = 13982, upload-time = "2024-02-02T16:30:46.06Z" }, + { url = "https://files.pythonhosted.org/packages/cb/06/0d28bd178db529c5ac762a625c335a9168a7a23f280b4db9c95e97046145/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", size = 26335, upload-time = "2024-02-02T16:30:47.676Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/c4f5016f87ced614eacc7d5fb85b25bcc0ff53e8f058d069fc8cbfdc3c7a/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", size = 25557, upload-time = "2024-02-02T16:30:48.936Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fb/c18b8c9fbe69e347fdbf782c6478f1bc77f19a830588daa224236678339b/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", size = 25245, upload-time = "2024-02-02T16:30:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/2f/69/30d29adcf9d1d931c75001dd85001adad7374381c9c2086154d9f6445be6/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", size = 31013, upload-time = "2024-02-02T16:30:51.795Z" }, + { url = "https://files.pythonhosted.org/packages/3a/03/63498d05bd54278b6ca340099e5b52ffb9cdf2ee4f2d9b98246337e21689/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", size = 30178, upload-time = "2024-02-02T16:30:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/68/79/11b4fe15124692f8673b603433e47abca199a08ecd2a4851bfbdc97dc62d/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", size = 30429, upload-time = "2024-02-02T16:30:53.983Z" }, + { url = "https://files.pythonhosted.org/packages/ed/88/408bdbf292eb86f03201c17489acafae8358ba4e120d92358308c15cea7c/MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", size = 16633, upload-time = "2024-02-02T16:30:55.317Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4c/3577a52eea1880538c435176bc85e5b3379b7ab442327ccd82118550758f/MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", size = 17215, upload-time = "2024-02-02T16:30:56.6Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192, upload-time = "2024-02-02T16:30:57.715Z" }, + { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072, upload-time = "2024-02-02T16:30:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928, upload-time = "2024-02-02T16:30:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106, upload-time = "2024-02-02T16:31:01.582Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781, upload-time = "2024-02-02T16:31:02.71Z" }, + { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518, upload-time = "2024-02-02T16:31:04.392Z" }, + { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669, upload-time = "2024-02-02T16:31:05.53Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933, upload-time = "2024-02-02T16:31:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656, upload-time = "2024-02-02T16:31:07.767Z" }, + { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206, upload-time = "2024-02-02T16:31:08.843Z" }, + { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193, upload-time = "2024-02-02T16:31:10.155Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073, upload-time = "2024-02-02T16:31:11.442Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486, upload-time = "2024-02-02T16:31:12.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685, upload-time = "2024-02-02T16:31:13.726Z" }, + { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338, upload-time = "2024-02-02T16:31:14.812Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439, upload-time = "2024-02-02T16:31:15.946Z" }, + { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531, upload-time = "2024-02-02T16:31:17.13Z" }, + { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823, upload-time = "2024-02-02T16:31:18.247Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658, upload-time = "2024-02-02T16:31:19.583Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211, upload-time = "2024-02-02T16:31:20.96Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "colorama", marker = "python_full_version < '3.8' and sys_platform == 'win32'" }, + { name = "ghp-import", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "jinja2", marker = "python_full_version < '3.8'" }, + { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mergedeep", marker = "python_full_version < '3.8'" }, + { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pathspec", version = "0.11.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "platformdirs", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyyaml-env-tag", version = "0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "watchdog", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/bb/24a22f8154cf79b07b45da070633613837d6e59c7d870076f693b7b1c556/mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2", size = 3654364, upload-time = "2023-09-18T21:26:11.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/58/aa3301b23966a71d7f8e55233f467b3cec94a651434e9cd9053811342539/mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1", size = 3694750, upload-time = "2023-09-18T21:26:09.089Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "colorama", marker = "python_full_version >= '3.8' and sys_platform == 'win32'" }, + { name = "ghp-import", marker = "python_full_version >= '3.8'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "jinja2", marker = "python_full_version >= '3.8'" }, + { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "mergedeep", marker = "python_full_version >= '3.8'" }, + { name = "mkdocs-get-deps", marker = "python_full_version >= '3.8'" }, + { name = "packaging", version = "25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "pathspec", version = "0.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pathspec", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "pyyaml-env-tag", version = "0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pyyaml-env-tag", version = "1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "watchdog", version = "4.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "watchdog", version = "6.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs", version = "1.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/3f/9531888bc92bafb1bffddca5d9240a7bae9a479d465528883b61808ef9d6/mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84", size = 13142, upload-time = "2022-03-07T16:43:49.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/5c/6594400290df38f99bf8d9ef249387b56f4ad962667836266f6fe7da8597/mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b", size = 9802, upload-time = "2022-03-07T16:43:47.394Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/ae/0f1154c614d6a8b8a36fff084e5b82af3a15f7d2060cf0dcdb1c53297a71/mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f", size = 40262, upload-time = "2024-09-01T18:29:18.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f", size = 16522, upload-time = "2024-09-01T18:29:16.605Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mergedeep", marker = "python_full_version >= '3.8'" }, + { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.2.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "babel", version = "2.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "colorama", marker = "python_full_version < '3.8'" }, + { name = "jinja2", marker = "python_full_version < '3.8'" }, + { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs", version = "1.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs-material-extensions", version = "1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "paginate", marker = "python_full_version < '3.8'" }, + { name = "pygments", version = "2.17.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pymdown-extensions", version = "10.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "regex", marker = "python_full_version < '3.8'" }, + { name = "requests", version = "2.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/20/d63a01b9890184e7bd7fffed915a0636f0682c74900b1b238bc216556049/mkdocs_material-9.2.7.tar.gz", hash = "sha256:b44da35b0d98cd762d09ef74f1ddce5b6d6e35c13f13beb0c9d82a629e5f229e", size = 3788625, upload-time = "2023-09-02T17:07:21.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/bd/f5d39a0c52865dbf03503d177dd05368a08d79e6c746331b5d685899ee63/mkdocs_material-9.2.7-py3-none-any.whl", hash = "sha256:92e4160d191cc76121fed14ab9f14638e43a6da0f2e9d7a9194d377f0a4e7f18", size = 8016474, upload-time = "2023-09-02T17:07:17.993Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "babel", version = "2.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "backrefs", version = "5.7.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "backrefs", version = "6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "colorama", marker = "python_full_version >= '3.8'" }, + { name = "jinja2", marker = "python_full_version >= '3.8'" }, + { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "mkdocs-material-extensions", version = "1.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "paginate", marker = "python_full_version >= '3.8'" }, + { name = "pygments", version = "2.19.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "pymdown-extensions", version = "10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pymdown-extensions", version = "10.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/ceb3b4fc3810184e6d21dbe909a289884d5d183f1830fd44bcbce8027c66/mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf", size = 11105, upload-time = "2023-09-20T15:44:35.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/db/6ada1f1cfd32808507c901ca4616f8c0907113c7a7c1eca7b03c89bb0fcf/mkdocs_material_extensions-1.2-py3-none-any.whl", hash = "sha256:c767bd6d6305f6420a50f0b541b0c9966d52068839af97029be14443849fb8a1", size = 7987, upload-time = "2023-09-20T15:44:33.972Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "jinja2", marker = "python_full_version < '3.8'" }, + { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs", version = "1.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocs-autorefs", version = "0.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pymdown-extensions", version = "10.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/89/39b7da1cd3d7bc9d3626a2030349443276bd4c8428b676b010ffb96ec9be/mkdocstrings-0.22.0.tar.gz", hash = "sha256:82a33b94150ebb3d4b5c73bab4598c3e21468c79ec072eff6931c8f3bfc38256", size = 30419, upload-time = "2023-05-26T10:45:12.878Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/26/5816407b5dd51821a3d23f53bdbd013ab1878b6246e520dc014d200ee1d2/mkdocstrings-0.22.0-py3-none-any.whl", hash = "sha256:2d4095d461554ff6a778fdabdca3c00c468c2f1459d469f7a7f622a2b23212ba", size = 26747, upload-time = "2023-05-26T10:45:10.475Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "0.26.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "jinja2", marker = "python_full_version == '3.8.*'" }, + { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocs-autorefs", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pymdown-extensions", version = "10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/170ff04de72227f715d67da32950c7b8434449f3805b2ec3dd1085db4d7c/mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33", size = 92677, upload-time = "2024-09-06T10:26:06.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cc/8ba127aaee5d1e9046b0d33fa5b3d17da95a9d705d44902792e0569257fd/mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf", size = 29643, upload-time = "2024-09-06T10:26:04.498Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "0.30.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "jinja2", marker = "python_full_version == '3.9.*'" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mkdocs-autorefs", version = "1.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pymdown-extensions", version = "10.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-autorefs", version = "1.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pymdown-extensions", version = "10.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086, upload-time = "2025-11-27T15:39:40.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135, upload-time = "2025-11-27T15:39:39.301Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "griffe", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mkdocstrings", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/22/eaa1ccd8aaeac72953a780f4b6c82650475d241dd953582706db547db004/mkdocstrings_python-1.1.2.tar.gz", hash = "sha256:f28bdcacb9bcdb44b6942a5642c1ea8b36870614d33e29e3c923e204a8d8ed61", size = 24613, upload-time = "2023-06-04T16:19:16.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/81/8c1de2573c4fa5fff7fc20eba1dad2892f43ed22ba145b595243e375c274/mkdocstrings_python-1.1.2-py3-none-any.whl", hash = "sha256:c2b652a850fec8e85034a9cdb3b45f8ad1a558686edc20ed1f40b4e17e62070f", size = 40366, upload-time = "2023-06-04T16:19:14.831Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "griffe", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocs-autorefs", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mkdocstrings", version = "0.26.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/ba/534c934cd0a809f51c91332d6ed278782ee4126b8ba8db02c2003f162b47/mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322", size = 166890, upload-time = "2024-09-03T17:20:54.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f2/2a2c48fda645ac6bbe73bcc974587a579092b6868e6ff8bc6d177f4db38a/mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af", size = 109297, upload-time = "2024-09-03T17:20:52.621Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mkdocs-autorefs", version = "1.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-autorefs", version = "1.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, +] + +[[package]] +name = "mypy" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "mypy-extensions", version = "1.0.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'" }, + { name = "typed-ast", marker = "python_full_version < '3.8'" }, + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/28/d8a8233ff167d06108e53b7aefb4a8d7350adbbf9d7abd980f17fdb7a3a6/mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b", size = 2855162, upload-time = "2023-06-25T23:22:54.364Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/3b/1c7363863b56c059f60a1dfdca9ac774a22ba64b7a4da0ee58ee53e5243f/mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8", size = 10451043, upload-time = "2023-06-25T23:22:02.502Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6f0df1874118839db1155fed62a4bd7e80c181367ff8ea07d40fbaffcfb4/mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878", size = 9542079, upload-time = "2023-06-25T23:22:37.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5c/deeac94fcccd11aa621e6b350df333e1b809b11443774ea67582cc0205da/mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd", size = 11974913, upload-time = "2023-06-25T23:21:14.603Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/de3c455c54e8cf5e37ea38705c1920f2df470389f8fc051084d2dd8c9c59/mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc", size = 12044492, upload-time = "2023-06-25T23:22:17.551Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d3/6f65357dcb68109946de70cd55bd2e60f10114f387471302f48d54ff5dae/mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1", size = 8831655, upload-time = "2023-06-25T23:21:40.201Z" }, + { url = "https://files.pythonhosted.org/packages/94/01/e34e37a044325af4d4af9825c15e8a0d26d89b5a9624b4d0908449d3411b/mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462", size = 10338636, upload-time = "2023-06-25T23:22:43.45Z" }, + { url = "https://files.pythonhosted.org/packages/92/58/ccc0b714ecbd1a64b34d8ce1c38763ff6431de1d82551904ecc3711fbe05/mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258", size = 9444172, upload-time = "2023-06-25T23:21:25.502Z" }, + { url = "https://files.pythonhosted.org/packages/73/72/dfc0b46e6905eafd598e7c48c0c4f2e232647e4e36547425c64e6c850495/mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2", size = 11855450, upload-time = "2023-06-25T23:21:37.234Z" }, + { url = "https://files.pythonhosted.org/packages/66/f4/60739a2d336f3adf5628e7c9b920d16e8af6dc078550d615e4ba2a1d7759/mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7", size = 11928679, upload-time = "2023-06-25T23:22:40.757Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/6ff2b55bf8b605a4cc898883654c2ca4dd4feedf0bb04ecaacf60d165cde/mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01", size = 8831134, upload-time = "2023-06-25T23:22:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/fb69dad9634af9f1dab69f8b4031d674592384b59c7171852b1fbed6de15/mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b", size = 10101278, upload-time = "2023-06-25T23:22:51.525Z" }, + { url = "https://files.pythonhosted.org/packages/65/f7/77339904a3415cadca5551f2ea0c74feefc9b7187636a292690788f4d4b3/mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b", size = 11643877, upload-time = "2023-06-25T23:22:20.963Z" }, + { url = "https://files.pythonhosted.org/packages/f5/93/ae39163ae84266d24d1fcf8ee1e2db1e0346e09de97570dd101a07ccf876/mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7", size = 11702718, upload-time = "2023-06-25T23:22:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/13/3b/3b7de921626547b36c34b91c74cfbda260210df7c49bd3d315015cfd6005/mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9", size = 8551181, upload-time = "2023-06-25T23:22:27.656Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/63bab763e4d44e1a7c341fb64496ddf20970780935596ffed9ed2d85eae7/mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042", size = 10390236, upload-time = "2023-06-25T23:21:30.367Z" }, + { url = "https://files.pythonhosted.org/packages/23/3f/54a87d933440416a1efd7a42b45f8cf22e353efe889eb3903cc34177ab44/mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3", size = 9496760, upload-time = "2023-06-25T23:21:33.753Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/26230b46e27724bd54f76cd73a2759eaaf35292b32ba64f36c7c47836d4b/mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6", size = 11927489, upload-time = "2023-06-25T23:21:58.934Z" }, + { url = "https://files.pythonhosted.org/packages/64/7d/156e721376951c449554942eedf4d53e9ca2a57e94bf0833ad2821d59bfa/mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f", size = 11990009, upload-time = "2023-06-25T23:21:55.909Z" }, + { url = "https://files.pythonhosted.org/packages/27/ab/21230851e8137c9ef9a095cc8cb70d8ff8cac21014e4b249ac7a9eae7df9/mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc", size = 8816535, upload-time = "2023-06-25T23:21:45.703Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1b/9050b5c444ef82c3d59bdbf21f91b259cf20b2ac1df37d55bc6b91d609a1/mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828", size = 10447897, upload-time = "2023-06-25T23:21:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/da/00/ac2b58b321d85cac25be0dcd1bc2427dfc6cf403283fc205a0031576f14b/mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3", size = 9534091, upload-time = "2023-06-25T23:22:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/26240f14e854a95af87d577b288d607ebe0ccb75cb37052f6386402f022d/mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816", size = 11970165, upload-time = "2023-06-25T23:22:05.673Z" }, + { url = "https://files.pythonhosted.org/packages/b7/34/a3edaec8762181bfe97439c7e094f4c2f411ed9b79ac8f4d72156e88d5ce/mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c", size = 12040792, upload-time = "2023-06-25T23:21:49.878Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f3/0d0622d5a83859a992b01741a7b97949d6fb9efc9f05f20a09f0df10dc1e/mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f", size = 8831367, upload-time = "2023-06-25T23:21:43.065Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9a/e13addb8d652cb068f835ac2746d9d42f85b730092f581bb17e2059c28f1/mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4", size = 2451741, upload-time = "2023-06-25T23:22:49.033Z" }, +] + +[[package]] +name = "mypy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "mypy-extensions", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" }, + { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" }, + { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" }, + { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" }, + { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" }, + { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" }, + { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" }, + { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" }, + { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" }, + { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" }, + { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" }, + { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" }, + { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" }, + { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31", size = 11143050, upload-time = "2024-12-30T16:38:29.743Z" }, + { url = "https://files.pythonhosted.org/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6", size = 10321087, upload-time = "2024-12-30T16:38:14.739Z" }, + { url = "https://files.pythonhosted.org/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319", size = 12066766, upload-time = "2024-12-30T16:38:47.038Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac", size = 12787111, upload-time = "2024-12-30T16:39:02.444Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b", size = 12974331, upload-time = "2024-12-30T16:38:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837", size = 9763210, upload-time = "2024-12-30T16:38:36.299Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493, upload-time = "2024-12-30T16:38:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702, upload-time = "2024-12-30T16:38:50.623Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104, upload-time = "2024-12-30T16:38:53.735Z" }, + { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167, upload-time = "2024-12-30T16:38:56.437Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834, upload-time = "2024-12-30T16:38:59.204Z" }, + { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231, upload-time = "2024-12-30T16:39:05.124Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "librt", marker = "python_full_version >= '3.9' and platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pathspec", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", size = 13102606, upload-time = "2025-12-15T05:02:46.833Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", size = 12164496, upload-time = "2025-12-15T05:03:41.947Z" }, + { url = "https://files.pythonhosted.org/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", size = 12772068, upload-time = "2025-12-15T05:02:53.689Z" }, + { url = "https://files.pythonhosted.org/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", size = 13520385, upload-time = "2025-12-15T05:02:38.328Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", size = 13796221, upload-time = "2025-12-15T05:03:22.147Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", size = 10055456, upload-time = "2025-12-15T05:03:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "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 = "25.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/2a/bd167cdf116d4f3539caaa4c332752aac0b3a0cc0174cdb302ee68933e81/pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3", size = 47032, upload-time = "2023-07-29T01:05:04.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/2a/9b1be29146139ef459188f5e420a66e835dda921208db600b7037093891f/pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", size = 29603, upload-time = "2023-07-29T01:05:02.656Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/28/e40d24d2e2eb23135f8533ad33d582359c7825623b1e022f9d460def7c05/platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731", size = 19914, upload-time = "2023-11-10T16:43:08.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/16/70be3b725073035aa5fc3229321d06e22e73e3e09f6af78dcfdf16c7636c/platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b", size = 17562, upload-time = "2023-11-10T16:43:06.949Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, 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 = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.17.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772, upload-time = "2023-11-21T20:43:53.875Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756, upload-time = "2023-11-21T20:43:49.423Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/41/7d67a7b6974fe3ffa03c817c9772f593535a85a72f4ba80af47168615098/pymdown_extensions-10.2.1.tar.gz", hash = "sha256:d0c534b4a5725a4be7ccef25d65a4c97dba58b54ad7c813babf0eb5ba9c81591", size = 784912, upload-time = "2023-08-30T15:17:13.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/5d/aaadfd7c9cc1a1a720c487fd28ecb18418728cd61dd3451e8a831e8030ea/pymdown_extensions-10.2.1-py3-none-any.whl", hash = "sha256:bded105eb8d93f88f2f821f00108cb70cef1269db6a40128c09c5f48bfc60ea4", size = 241051, upload-time = "2023-08-30T15:17:11.449Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.15" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/92/a7296491dbf5585b3a987f3f3fc87af0e632121ff3e490c14b5f2d2b4eb5/pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7", size = 852320, upload-time = "2025-04-27T23:48:29.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f", size = 265845, upload-time = "2025-04-27T23:48:27.359Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.20" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" }, +] + +[[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", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, 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.5" +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 = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "packaging", version = "25.0", 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.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.9.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "packaging", version = "25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pygments", version = "2.19.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", version = "25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pygments", version = "2.19.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245, upload-time = "2023-05-24T18:44:56.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949, upload-time = "2023-05-24T18:44:54.079Z" }, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201, upload-time = "2023-07-18T00:00:23.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/06/4beb652c0fe16834032e54f0956443d4cc797fe645527acee59e7deaa0a2/PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", size = 189447, upload-time = "2023-07-17T23:57:04.325Z" }, + { url = "https://files.pythonhosted.org/packages/5b/07/10033a403b23405a8fc48975444463d3d10a5c2736b7eb2550b07b367429/PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", size = 169264, upload-time = "2023-07-17T23:57:07.787Z" }, + { url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", size = 677003, upload-time = "2023-07-17T23:57:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/090818dfa62e85181f3ae23dd1e8b7ea7f09684864a900cab72d29c57346/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", size = 699070, upload-time = "2023-07-17T23:57:19.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", size = 705525, upload-time = "2023-07-17T23:57:25.272Z" }, + { url = "https://files.pythonhosted.org/packages/07/91/45dfd0ef821a7f41d9d0136ea3608bb5b1653e42fd56a7970532cb5c003f/PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", size = 707514, upload-time = "2023-08-28T18:43:20.945Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a0/b6700da5d49e9fed49dc3243d3771b598dad07abb37cc32e524607f96adc/PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", size = 130488, upload-time = "2023-07-17T23:57:28.144Z" }, + { url = "https://files.pythonhosted.org/packages/24/97/9b59b43431f98d01806b288532da38099cc6f2fea0f3d712e21e269c0279/PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", size = 145338, upload-time = "2023-07-17T23:57:31.118Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0d/26fb23e8863e0aeaac0c64e03fd27367ad2ae3f3cccf3798ee98ce160368/PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", size = 187867, upload-time = "2023-07-17T23:57:34.35Z" }, + { url = "https://files.pythonhosted.org/packages/28/09/55f715ddbf95a054b764b547f617e22f1d5e45d83905660e9a088078fe67/PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", size = 167530, upload-time = "2023-07-17T23:57:36.975Z" }, + { url = "https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", size = 732244, upload-time = "2023-07-17T23:57:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/06/92/e0224aa6ebf9dc54a06a4609da37da40bb08d126f5535d81bff6b417b2ae/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", size = 752871, upload-time = "2023-07-17T23:57:51.921Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", size = 757729, upload-time = "2023-07-17T23:57:59.865Z" }, + { url = "https://files.pythonhosted.org/packages/03/5c/c4671451b2f1d76ebe352c0945d4cd13500adb5d05f5a51ee296d80152f7/PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", size = 748528, upload-time = "2023-08-28T18:43:23.207Z" }, + { url = "https://files.pythonhosted.org/packages/73/9c/766e78d1efc0d1fca637a6b62cea1b4510a7fb93617eb805223294fef681/PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", size = 130286, upload-time = "2023-07-17T23:58:02.964Z" }, + { url = "https://files.pythonhosted.org/packages/b3/34/65bb4b2d7908044963ebf614fe0fdb080773fc7030d7e39c8d3eddcd4257/PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", size = 144699, upload-time = "2023-07-17T23:58:05.586Z" }, + { url = "https://files.pythonhosted.org/packages/bc/06/1b305bf6aa704343be85444c9d011f626c763abb40c0edc1cad13bfd7f86/PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", size = 178692, upload-time = "2023-08-28T18:43:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/84/02/404de95ced348b73dd84f70e15a41843d817ff8c1744516bf78358f2ffd2/PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", size = 165622, upload-time = "2023-08-28T18:43:26.54Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4c/4a2908632fc980da6d918b9de9c1d9d7d7e70b2672b1ad5166ed27841ef7/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", size = 696937, upload-time = "2024-01-18T20:40:22.92Z" }, + { url = "https://files.pythonhosted.org/packages/b4/33/720548182ffa8344418126017aa1d4ab4aeec9a2275f04ce3f3573d8ace8/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", size = 724969, upload-time = "2023-08-28T18:43:28.56Z" }, + { url = "https://files.pythonhosted.org/packages/4f/78/77b40157b6cb5f2d3d31a3d9b2efd1ba3505371f76730d267e8b32cf4b7f/PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", size = 712604, upload-time = "2023-08-28T18:43:30.206Z" }, + { url = "https://files.pythonhosted.org/packages/2e/97/3e0e089ee85e840f4b15bfa00e4e63d84a3691ababbfea92d6f820ea6f21/PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", size = 126098, upload-time = "2023-08-28T18:43:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/fbade56564ad486809c27b322d0f7e6a89c01f6b4fe208402e90d4443a99/PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", size = 138675, upload-time = "2023-08-28T18:43:33.613Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d1/02baa09d39b1bb1ebaf0d850d106d1bdcb47c91958557f471153c49dc03b/PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", size = 189627, upload-time = "2023-07-17T23:58:40.188Z" }, + { url = "https://files.pythonhosted.org/packages/e5/31/ba812efa640a264dbefd258986a5e4e786230cb1ee4a9f54eb28ca01e14a/PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", size = 658438, upload-time = "2023-07-17T23:58:48.34Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f1/08f06159739254c8947899c9fc901241614195db15ba8802ff142237664c/PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", size = 680304, upload-time = "2023-07-17T23:58:57.396Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8f/db62b0df635b9008fe90aa68424e99cee05e68b398740c8a666a98455589/PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", size = 670140, upload-time = "2023-07-17T23:59:04.291Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5c/fcabd17918348c7db2eeeb0575705aaf3f7ab1657f6ce29b2e31737dd5d1/PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", size = 137577, upload-time = "2023-07-17T23:59:07.267Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ae/964ccb88a938f20ece5754878f182cfbd846924930d02d29d06af8d4c69e/PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", size = 153248, upload-time = "2023-07-17T23:59:10.608Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5d/2779ea035ba1e533c32ed4a249b4e0448f583ba10830b21a3cddafe11a4e/PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", size = 191734, upload-time = "2023-07-17T23:59:13.869Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a1/27bfac14b90adaaccf8c8289f441e9f76d94795ec1e7a8f134d9f2cb3d0b/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", size = 723767, upload-time = "2023-07-17T23:59:20.686Z" }, + { url = "https://files.pythonhosted.org/packages/c1/39/47ed4d65beec9ce07267b014be85ed9c204fa373515355d3efa62d19d892/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", size = 749067, upload-time = "2023-07-17T23:59:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/c8/6b/6600ac24725c7388255b2f5add93f91e58a5d7efaf4af244fdbcc11a541b/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", size = 736569, upload-time = "2023-07-17T23:59:37.216Z" }, + { url = "https://files.pythonhosted.org/packages/0d/46/62ae77677e532c0af6c81ddd6f3dbc16bdcc1208b077457354442d220bfb/PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", size = 787738, upload-time = "2023-08-28T18:43:35.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6a/439d1a6f834b9a9db16332ce16c4a96dd0e3970b65fe08cbecd1711eeb77/PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", size = 139797, upload-time = "2023-07-17T23:59:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/29/0f/9782fa5b10152abf033aec56a601177ead85ee03b57781f2d9fced09eefc/PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", size = 157350, upload-time = "2023-07-17T23:59:42.94Z" }, + { url = "https://files.pythonhosted.org/packages/57/c5/5d09b66b41d549914802f482a2118d925d876dc2a35b2d127694c1345c34/PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", size = 197846, upload-time = "2023-07-17T23:59:46.424Z" }, + { url = "https://files.pythonhosted.org/packages/0e/88/21b2f16cb2123c1e9375f2c93486e35fdc86e63f02e274f0e99c589ef153/PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", size = 174396, upload-time = "2023-07-17T23:59:49.538Z" }, + { url = "https://files.pythonhosted.org/packages/ac/6c/967d91a8edf98d2b2b01d149bd9e51b8f9fb527c98d80ebb60c6b21d60c4/PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", size = 731824, upload-time = "2023-07-17T23:59:58.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4b/c71ef18ef83c82f99e6da8332910692af78ea32bd1d1d76c9787dfa36aea/PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", size = 754777, upload-time = "2023-07-18T00:00:06.716Z" }, + { url = "https://files.pythonhosted.org/packages/7d/39/472f2554a0f1e825bd7c5afc11c817cd7a2f3657460f7159f691fbb37c51/PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", size = 738883, upload-time = "2023-07-18T00:00:14.423Z" }, + { url = "https://files.pythonhosted.org/packages/40/da/a175a35cf5583580e90ac3e2a3dbca90e43011593ae62ce63f79d7b28d92/PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", size = 750294, upload-time = "2023-08-28T18:43:37.153Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/7fcc372442ec8ea331da18c24b13710e010c5073ab851ef36bf9dacb283f/PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", size = 136936, upload-time = "2023-07-18T00:00:17.167Z" }, + { url = "https://files.pythonhosted.org/packages/84/4d/82704d1ab9290b03da94e6425f5e87396b999fd7eb8e08f3a92c158402bf/PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", size = 152751, upload-time = "2023-07-18T00:00:19.939Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/a2/09f67a3589cb4320fb5ce90d3fd4c9752636b8b6ad8f34b54d76c5a54693/PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f", size = 186824, upload-time = "2025-09-29T20:27:35.918Z" }, + { url = "https://files.pythonhosted.org/packages/02/72/d972384252432d57f248767556ac083793292a4adf4e2d85dfe785ec2659/PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4", size = 795069, upload-time = "2025-09-29T20:27:38.15Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3b/6c58ac0fa7c4e1b35e48024eb03d00817438310447f93ef4431673c24138/PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3", size = 862585, upload-time = "2025-09-29T20:27:39.715Z" }, + { url = "https://files.pythonhosted.org/packages/25/a2/b725b61ac76a75583ae7104b3209f75ea44b13cfd026aa535ece22b7f22e/PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6", size = 806018, upload-time = "2025-09-29T20:27:41.444Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/b2227677b2d1036d84f5ee95eb948e7af53d59fe3e4328784e4d290607e0/PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369", size = 802822, upload-time = "2025-09-29T20:27:42.885Z" }, + { url = "https://files.pythonhosted.org/packages/99/a5/718a8ea22521e06ef19f91945766a892c5ceb1855df6adbde67d997ea7ed/PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295", size = 143744, upload-time = "2025-09-29T20:27:44.487Z" }, + { url = "https://files.pythonhosted.org/packages/76/b2/2b69cee94c9eb215216fc05778675c393e3aa541131dc910df8e52c83776/PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b", size = 160082, upload-time = "2025-09-29T20:27:46.049Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", + "python_full_version < '3.8'", +] +dependencies = [ + { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631, upload-time = "2020-11-12T02:38:26.239Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911, upload-time = "2020-11-12T02:38:24.638Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "regex" +version = "2022.10.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b5/92d404279fd5f4f0a17235211bb0f5ae7a0d9afb7f439086ec247441ed28/regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83", size = 391554, upload-time = "2022-10-31T03:30:47.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/93/67595e62890fa944da394795f0425140917340d35d9cfd49672a8dc48c1a/regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f", size = 293917, upload-time = "2022-10-31T03:26:14.12Z" }, + { url = "https://files.pythonhosted.org/packages/8d/50/7dd264adf08bf3ca588562bac344a825174e8e57c75ad3e5ed169aba5718/regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9", size = 287178, upload-time = "2022-10-31T03:26:18.034Z" }, + { url = "https://files.pythonhosted.org/packages/30/eb/a28fad5b882d3e711c75414b3c99fb2954f78fa450deeed9fe9ad3bf2534/regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b", size = 769845, upload-time = "2022-10-31T03:26:20.534Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ba/92096d78cbdd34dce674962392a0e57ce748a9e5f737f12b0001723d959a/regex-2022.10.31-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57", size = 809592, upload-time = "2022-10-31T03:26:23.597Z" }, + { url = "https://files.pythonhosted.org/packages/48/1e/829551abceba73e7e9b1f94a311a53e9c0f60c7deec8821633fc3b343a58/regex-2022.10.31-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4", size = 795944, upload-time = "2022-10-31T03:26:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/be/d3/7e334b8bc597dea6200f7bb969fc693d4c71c4a395750e28d09c8e5a8104/regex-2022.10.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001", size = 770479, upload-time = "2022-10-31T03:26:31.625Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ca/105a8f6d70499f2687a857570dcd411c0621a347b06c27126cffc32e77e0/regex-2022.10.31-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90", size = 757876, upload-time = "2022-10-31T03:26:34.351Z" }, + { url = "https://files.pythonhosted.org/packages/7c/cf/50844f62052bb858987fe3970315134e3be6167fc76e11d328e7fcf876ff/regex-2022.10.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144", size = 685151, upload-time = "2022-10-31T03:26:39.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c2/6d41a7a9690d4543b1f438f45576af96523c4f1caeb5307fff3350ec7d0b/regex-2022.10.31-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc", size = 739977, upload-time = "2022-10-31T03:26:43.157Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/49b9a2cb289c20888b23bb7f8f29e3ad7982785b10041477fd56ed5783c5/regex-2022.10.31-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66", size = 728265, upload-time = "2022-10-31T03:26:45.564Z" }, + { url = "https://files.pythonhosted.org/packages/08/cb/0445a970e755eb806945a166729210861391f645223187aa11fcbbb606ce/regex-2022.10.31-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af", size = 762419, upload-time = "2022-10-31T03:26:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/23/8d/1df5d30ce1e5ae3edfb775b892c93882d13ba93991314871fec569f16829/regex-2022.10.31-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc", size = 763152, upload-time = "2022-10-31T03:26:51.093Z" }, + { url = "https://files.pythonhosted.org/packages/00/7e/ab5a54f60e36f4de0610850866b848839a7b02ad4f05755bce429fbc1a5a/regex-2022.10.31-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66", size = 741683, upload-time = "2022-10-31T03:26:53.738Z" }, + { url = "https://files.pythonhosted.org/packages/2d/db/45ca83007d69cc594c32d7feae20b1b6067f829b2b0d27bb769d7188dfa1/regex-2022.10.31-cp310-cp310-win32.whl", hash = "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1", size = 255766, upload-time = "2022-10-31T03:26:56.473Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0a/c865345e6ece671f16ac1fe79bf4ba771c528c2e4a56607898cdf065c285/regex-2022.10.31-cp310-cp310-win_amd64.whl", hash = "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5", size = 267701, upload-time = "2022-10-31T03:26:59.275Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1a/e7ae9a041d3e103f98c9a79d8abb235cca738b7bd6da3fb5e4066d30e4d7/regex-2022.10.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe", size = 293971, upload-time = "2022-10-31T03:27:01.908Z" }, + { url = "https://files.pythonhosted.org/packages/fa/54/acb97b65bc556520d61262ff22ad7d4baff96e3219fa1dc5425269def873/regex-2022.10.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542", size = 287195, upload-time = "2022-10-31T03:27:05.141Z" }, + { url = "https://files.pythonhosted.org/packages/c1/65/3ee862c7a78ce1f9bd748d460e379317464c2658e645a1a7c1304d36e819/regex-2022.10.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7", size = 781858, upload-time = "2022-10-31T03:27:08.773Z" }, + { url = "https://files.pythonhosted.org/packages/55/73/f71734c0357e41673b00bff0a8675ffb67328ba18f24614ec5af2073b56f/regex-2022.10.31-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e", size = 821531, upload-time = "2022-10-31T03:27:11.668Z" }, + { url = "https://files.pythonhosted.org/packages/83/ad/defd48762ff8fb2d06667b1e8bef471c2cc71a1b3d6ead26b841bfd9da99/regex-2022.10.31-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c", size = 808819, upload-time = "2022-10-31T03:27:15.369Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cf/97a89e2b798988118beed6620dbfbc9b4bd72d8177b3b4ed47d80da26df9/regex-2022.10.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1", size = 781085, upload-time = "2022-10-31T03:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/fd/12/c5d64d860c2d1be211a91b2416097d5e40699b80296cb4e99a064d4b4ff2/regex-2022.10.31-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4", size = 769611, upload-time = "2022-10-31T03:27:20.957Z" }, + { url = "https://files.pythonhosted.org/packages/04/de/e8ed731b334e5f962ef035a32f151fffb2f839eccfba40c3ebdac9b26e03/regex-2022.10.31-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f", size = 746313, upload-time = "2022-10-31T03:27:23.905Z" }, + { url = "https://files.pythonhosted.org/packages/18/9c/b52170b2dc8d65a69f3369d0bd1a3102df295edfccfef1b41e82b6aef796/regex-2022.10.31-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5", size = 737036, upload-time = "2022-10-31T03:27:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a6/2af9cc002057b75868ec7740fe3acb8f89796c9d29caf5775fefd96c3240/regex-2022.10.31-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c", size = 771052, upload-time = "2022-10-31T03:27:32.689Z" }, + { url = "https://files.pythonhosted.org/packages/87/50/e237090e90a0b0c8eab40af7d6f2faaf1432c4dca232de9a9c789faf3154/regex-2022.10.31-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c", size = 772708, upload-time = "2022-10-31T03:27:35.745Z" }, + { url = "https://files.pythonhosted.org/packages/07/ba/7021c60d02f7fe7c3e4ee9636d8a2d93bd894a5063c2b5f35e2e31b1f3ad/regex-2022.10.31-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7", size = 750463, upload-time = "2022-10-31T03:27:39.832Z" }, + { url = "https://files.pythonhosted.org/packages/08/28/f038ff3c5cfd30760bccefbe0b98d51cf61192ec8d3d55dd51564bf6c6b8/regex-2022.10.31-cp311-cp311-win32.whl", hash = "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af", size = 255769, upload-time = "2022-10-31T03:27:42.847Z" }, + { url = "https://files.pythonhosted.org/packages/91/4e/fb78efdac24862ef6ea8009b0b9cdb5f25968d1b262cc32abd9d483f50b1/regex-2022.10.31-cp311-cp311-win_amd64.whl", hash = "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61", size = 267703, upload-time = "2022-10-31T03:27:45.728Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4a/48779981af80558ac01f0f2c0d71c1214215bc74c9b824eb6581e94a847c/regex-2022.10.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8", size = 294374, upload-time = "2022-10-31T03:28:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/56/e3/351029c41f42e29d9c6ae3d217ad332761945b41dfbddb64adc31d434c6b/regex-2022.10.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783", size = 752940, upload-time = "2022-10-31T03:28:32.341Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4f/33b5cbd85fb0272e5c1dc00e3cfc89874b37705613455d7ab1c1f3ff7906/regex-2022.10.31-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347", size = 794992, upload-time = "2022-10-31T03:28:35.197Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7d/0b0d25b7bb9a38cdccffd3fdcbf4ad7dd124fdf6ca6067cd973edff804bc/regex-2022.10.31-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93", size = 781796, upload-time = "2022-10-31T03:28:38.149Z" }, + { url = "https://files.pythonhosted.org/packages/42/d8/8a7131e7d0bf237f7bcd3191541a4bf21863c253fe6bee0796900a1a9a29/regex-2022.10.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6", size = 757070, upload-time = "2022-10-31T03:28:41.442Z" }, + { url = "https://files.pythonhosted.org/packages/0a/cd/4dfdfddca4478ad0ebb6053b2c2923eef1a8660ceb9f495e7a6abb62da15/regex-2022.10.31-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11", size = 743506, upload-time = "2022-10-31T03:28:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f2/20be658beb9ebef677550be562eae86c5433119b4b2fdb67035e9a841b0f/regex-2022.10.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec", size = 676318, upload-time = "2022-10-31T03:28:47.841Z" }, + { url = "https://files.pythonhosted.org/packages/43/5b/6ba9b08ea991993ad61e4098d88069c86f6d6cc0e52a26fa35f6a66d90ee/regex-2022.10.31-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9", size = 728650, upload-time = "2022-10-31T03:28:50.887Z" }, + { url = "https://files.pythonhosted.org/packages/a3/60/6084d08f56d424f46ecbfedebd11b2c2d7eb2f9bc36ccd8801821024262c/regex-2022.10.31-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1", size = 718298, upload-time = "2022-10-31T03:28:53.734Z" }, + { url = "https://files.pythonhosted.org/packages/10/13/95d658ca010507b5a179d7fe8376d37d20c22f9be5abdd301832618463a8/regex-2022.10.31-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8", size = 752789, upload-time = "2022-10-31T03:28:56.649Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/b6819a467182e94e7648120cedcb6019751ceff9f5f3ef9c340e14ea7992/regex-2022.10.31-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5", size = 754164, upload-time = "2022-10-31T03:28:59.321Z" }, + { url = "https://files.pythonhosted.org/packages/00/92/25b0b709d591ecd27e1bfb48c64d813a4ed4be0feb0321ea0b55db012099/regex-2022.10.31-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95", size = 730547, upload-time = "2022-10-31T03:29:02.247Z" }, + { url = "https://files.pythonhosted.org/packages/c2/52/b71ff1a281f37016cab322e176e3c63fe1b5c27d68cdacdec769708e49b7/regex-2022.10.31-cp37-cp37m-win32.whl", hash = "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394", size = 255505, upload-time = "2022-10-31T03:29:04.783Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/a01602507224e611caa3c0f2a4aa96f4c03fdce482fa4527de61678a3018/regex-2022.10.31-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0", size = 268006, upload-time = "2022-10-31T03:29:07.245Z" }, + { url = "https://files.pythonhosted.org/packages/ad/29/4efb589803fa476e649fcc256886837b74931c4ca1878e69cd5018f77e03/regex-2022.10.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d", size = 294035, upload-time = "2022-10-31T03:29:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/78/74/c8659c8e1b6745299df62099d162002deeb32a9a933bc7632836a3c22374/regex-2022.10.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8", size = 287154, upload-time = "2022-10-31T03:29:13.831Z" }, + { url = "https://files.pythonhosted.org/packages/dd/08/67feb849ab7288465b7b577cf076c0db5244dfd64bec8740cd8f0e074897/regex-2022.10.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad", size = 771084, upload-time = "2022-10-31T03:29:16.447Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/daeb6806a2b2e10e548c95b136aefb12818ef81a0aa5f865705bf19e7cd7/regex-2022.10.31-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee", size = 811472, upload-time = "2022-10-31T03:29:19.715Z" }, + { url = "https://files.pythonhosted.org/packages/56/4b/22c965c2f6847b0581a8d4407b265c04f989cb6df09ddfd7205744b14cbc/regex-2022.10.31-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714", size = 796329, upload-time = "2022-10-31T03:29:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/21/1f/f54c156ac95a89d33113d78a18c03db8c00600392d6d6c5a18249c563c58/regex-2022.10.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e", size = 772348, upload-time = "2022-10-31T03:29:25.809Z" }, + { url = "https://files.pythonhosted.org/packages/b3/60/38ea6f8808bf58852b3e08faa2d7418b8887144f891284bc2a1afb7b6967/regex-2022.10.31-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6", size = 760439, upload-time = "2022-10-31T03:29:29.02Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1a/63bcd0f28f74619190c4f6f3cf90e3fdccb4b1437aac7e19598e18b51901/regex-2022.10.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318", size = 690577, upload-time = "2022-10-31T03:29:32.414Z" }, + { url = "https://files.pythonhosted.org/packages/72/cf/da36a722626572ea66ab799e7019eb9a367fa563d43e3b1ec65a934d12d3/regex-2022.10.31-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff", size = 744029, upload-time = "2022-10-31T03:29:35.626Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/96ef949ee331d39489799b44f2d5aa8a252a2d7aa4a96edbb05425d344f6/regex-2022.10.31-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a", size = 732602, upload-time = "2022-10-31T03:29:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5c/40e197174793b44637dd542c1dee45a5517023d1cac5ca5a68fbe60e4105/regex-2022.10.31-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73", size = 766937, upload-time = "2022-10-31T03:29:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/08/e2/94af654d5fdfdad3a05991e104df66c42945650d31713fe290cd446178f1/regex-2022.10.31-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d", size = 767790, upload-time = "2022-10-31T03:29:45.209Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/70714b99c25bac40f81eaf3fe06eb016c5b9b9ac88815145dc6aa7d06b68/regex-2022.10.31-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c", size = 747112, upload-time = "2022-10-31T03:29:48.876Z" }, + { url = "https://files.pythonhosted.org/packages/63/89/7035055b960428a3af1fb1bfdf805cada83a81f88459350dad82a260a08d/regex-2022.10.31-cp38-cp38-win32.whl", hash = "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc", size = 255779, upload-time = "2022-10-31T03:29:51.914Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/895ba11bc0243becd38f8b7560d2e329c465ead247cfb815611c347d7fc1/regex-2022.10.31-cp38-cp38-win_amd64.whl", hash = "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453", size = 267716, upload-time = "2022-10-31T03:29:54.755Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6a/386254696e2ab59ccce2eeee1e014f95538004e3c840606ef817192dbf8a/regex-2022.10.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49", size = 293917, upload-time = "2022-10-31T03:29:58.174Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7e/23ddf7d405aad0d0a8fa478ba60fc1c46f661403fe4a49e04d48ea1095b4/regex-2022.10.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b", size = 287193, upload-time = "2022-10-31T03:30:01.127Z" }, + { url = "https://files.pythonhosted.org/packages/58/4e/0f0a7b674d6164809db80eac36a3a70bbd3bcf6dc8fb6f89f70f0893b85b/regex-2022.10.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc", size = 769260, upload-time = "2022-10-31T03:30:04.244Z" }, + { url = "https://files.pythonhosted.org/packages/59/68/5d77731c6cb3cfcf8aece4c650cc4a601795387292e2bd61826ed75310eb/regex-2022.10.31-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244", size = 809109, upload-time = "2022-10-31T03:30:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/ad/56/c6344d2f3e170229fbd9e7928f85969084905e52ea06446f4d1763c712ce/regex-2022.10.31-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690", size = 795308, upload-time = "2022-10-31T03:30:12.711Z" }, + { url = "https://files.pythonhosted.org/packages/de/82/1e868572aaa6b5468f07512fd184650bf9ade15943d4f1ae83d0dc512872/regex-2022.10.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185", size = 769980, upload-time = "2022-10-31T03:30:15.424Z" }, + { url = "https://files.pythonhosted.org/packages/69/a4/d8cb52db0a918f8a1cad766c4bc5cf968b2a00a06183aa9b5f71ff6094e3/regex-2022.10.31-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7", size = 757362, upload-time = "2022-10-31T03:30:19.225Z" }, + { url = "https://files.pythonhosted.org/packages/28/9c/e392e9aac4d4c10d81e0991e31e50755bd5f15a924284de4fac1d728b145/regex-2022.10.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4", size = 684624, upload-time = "2022-10-31T03:30:22.433Z" }, + { url = "https://files.pythonhosted.org/packages/4e/fa/efe2c65d2555a01c61a6522b63f98dd7f77dbfeea810e96d8f7e1d9552a3/regex-2022.10.31-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5", size = 739283, upload-time = "2022-10-31T03:30:25.135Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a2/1c165d7759f501184214e788dccfc0bbca068eb70d6bc4fd7999712a2674/regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1", size = 727792, upload-time = "2022-10-31T03:30:28.094Z" }, + { url = "https://files.pythonhosted.org/packages/ec/26/6577862030d42967657f1132956c4600a95bb7e999741bfa32cc0c5441ff/regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8", size = 761812, upload-time = "2022-10-31T03:30:31.116Z" }, + { url = "https://files.pythonhosted.org/packages/cc/45/1ecb7ee4f479da2bc23e16a0266a90a5ecd918e1410d9188a1ae457f7c3e/regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8", size = 762574, upload-time = "2022-10-31T03:30:34.142Z" }, + { url = "https://files.pythonhosted.org/packages/48/4e/4c1e7dfab3255f4476faa11a9fcc867e03d2c4abb2e101505deb7ef790e0/regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892", size = 741567, upload-time = "2022-10-31T03:30:37.561Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ac/519de46093b4162e154f055ec020ba2f3641ba2cf6f1ddefd1abea5043b3/regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1", size = 255797, upload-time = "2022-10-31T03:30:40.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/3c/17432c77b7d3929adb73077584606b236be4ed832243d426f51f5a0f72f9/regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692", size = 267752, upload-time = "2022-10-31T03:30:44.09Z" }, +] + +[[package]] +name = "requests" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.8'" }, + { name = "charset-normalizer", marker = "python_full_version < '3.8'" }, + { name = "idna", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "urllib3", version = "2.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version == '3.8.*'" }, + { name = "charset-normalizer", marker = "python_full_version == '3.8.*'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.9'" }, + { name = "charset-normalizer", marker = "python_full_version >= '3.9'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/77/9a7fe084d268f8855d493e5031ea03fa0af8cc05887f638bf1c4e3363eb8/ruff-0.14.11.tar.gz", hash = "sha256:f6dc463bfa5c07a59b1ff2c3b9767373e541346ea105503b4c0369c520a66958", size = 5993417, upload-time = "2026-01-08T19:11:58.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/a6/a4c40a5aaa7e331f245d2dc1ac8ece306681f52b636b40ef87c88b9f7afd/ruff-0.14.11-py3-none-linux_armv6l.whl", hash = "sha256:f6ff2d95cbd335841a7217bdfd9c1d2e44eac2c584197ab1385579d55ff8830e", size = 12951208, upload-time = "2026-01-08T19:12:09.218Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/360a35cb7204b328b685d3129c08aca24765ff92b5a7efedbdd6c150d555/ruff-0.14.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f6eb5c1c8033680f4172ea9c8d3706c156223010b8b97b05e82c59bdc774ee6", size = 13330075, upload-time = "2026-01-08T19:12:02.549Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9e/0cc2f1be7a7d33cae541824cf3f95b4ff40d03557b575912b5b70273c9ec/ruff-0.14.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2fc34cc896f90080fca01259f96c566f74069a04b25b6205d55379d12a6855e", size = 12257809, upload-time = "2026-01-08T19:12:00.366Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e5/5faab97c15bb75228d9f74637e775d26ac703cc2b4898564c01ab3637c02/ruff-0.14.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53386375001773ae812b43205d6064dae49ff0968774e6befe16a994fc233caa", size = 12678447, upload-time = "2026-01-08T19:12:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/e9767f60a2bef779fb5855cab0af76c488e0ce90f7bb7b8a45c8a2ba4178/ruff-0.14.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a697737dce1ca97a0a55b5ff0434ee7205943d4874d638fe3ae66166ff46edbe", size = 12758560, upload-time = "2026-01-08T19:11:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/eb/84/4c6cf627a21462bb5102f7be2a320b084228ff26e105510cd2255ea868e5/ruff-0.14.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6845ca1da8ab81ab1dce755a32ad13f1db72e7fba27c486d5d90d65e04d17b8f", size = 13599296, upload-time = "2026-01-08T19:11:30.371Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/92b5ed7ea66d849f6157e695dc23d5d6d982bd6aa8d077895652c38a7cae/ruff-0.14.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e36ce2fd31b54065ec6f76cb08d60159e1b32bdf08507862e32f47e6dde8bcbf", size = 15048981, upload-time = "2026-01-08T19:12:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/61/df/c1bd30992615ac17c2fb64b8a7376ca22c04a70555b5d05b8f717163cf9f/ruff-0.14.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590bcc0e2097ecf74e62a5c10a6b71f008ad82eb97b0a0079e85defe19fe74d9", size = 14633183, upload-time = "2026-01-08T19:11:40.069Z" }, + { url = "https://files.pythonhosted.org/packages/04/e9/fe552902f25013dd28a5428a42347d9ad20c4b534834a325a28305747d64/ruff-0.14.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53fe71125fc158210d57fe4da26e622c9c294022988d08d9347ec1cf782adafe", size = 14050453, upload-time = "2026-01-08T19:11:37.555Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/f36d89fa021543187f98991609ce6e47e24f35f008dfe1af01379d248a41/ruff-0.14.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35c9da08562f1598ded8470fcfef2afb5cf881996e6c0a502ceb61f4bc9c8a3", size = 13757889, upload-time = "2026-01-08T19:12:07.094Z" }, + { url = "https://files.pythonhosted.org/packages/b7/9f/c7fb6ecf554f28709a6a1f2a7f74750d400979e8cd47ed29feeaa1bd4db8/ruff-0.14.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0f3727189a52179393ecf92ec7057c2210203e6af2676f08d92140d3e1ee72c1", size = 13955832, upload-time = "2026-01-08T19:11:55.064Z" }, + { url = "https://files.pythonhosted.org/packages/db/a0/153315310f250f76900a98278cf878c64dfb6d044e184491dd3289796734/ruff-0.14.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eb09f849bd37147a789b85995ff734a6c4a095bed5fd1608c4f56afc3634cde2", size = 12586522, upload-time = "2026-01-08T19:11:35.356Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2b/a73a2b6e6d2df1d74bf2b78098be1572191e54bec0e59e29382d13c3adc5/ruff-0.14.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c61782543c1231bf71041461c1f28c64b961d457d0f238ac388e2ab173d7ecb7", size = 12724637, upload-time = "2026-01-08T19:11:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/f0/41/09100590320394401cd3c48fc718a8ba71c7ddb1ffd07e0ad6576b3a3df2/ruff-0.14.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82ff352ea68fb6766140381748e1f67f83c39860b6446966cff48a315c3e2491", size = 13145837, upload-time = "2026-01-08T19:11:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d8/e035db859d1d3edf909381eb8ff3e89a672d6572e9454093538fe6f164b0/ruff-0.14.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:728e56879df4ca5b62a9dde2dd0eb0edda2a55160c0ea28c4025f18c03f86984", size = 13850469, upload-time = "2026-01-08T19:12:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/4e/02/bb3ff8b6e6d02ce9e3740f4c17dfbbfb55f34c789c139e9cd91985f356c7/ruff-0.14.11-py3-none-win32.whl", hash = "sha256:337c5dd11f16ee52ae217757d9b82a26400be7efac883e9e852646f1557ed841", size = 12851094, upload-time = "2026-01-08T19:11:45.163Z" }, + { url = "https://files.pythonhosted.org/packages/58/f1/90ddc533918d3a2ad628bc3044cdfc094949e6d4b929220c3f0eb8a1c998/ruff-0.14.11-py3-none-win_amd64.whl", hash = "sha256:f981cea63d08456b2c070e64b79cb62f951aa1305282974d4d5216e6e0178ae6", size = 14001379, upload-time = "2026-01-08T19:11:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/1dbe51782c0e1e9cfce1d1004752672d2d4629ea46945d19d731ad772b3b/ruff-0.14.11-py3-none-win_arm64.whl", hash = "sha256:649fb6c9edd7f751db276ef42df1f3df41c38d67d199570ae2a7bd6cbc3590f0", size = 12938644, upload-time = "2026-01-08T19:11:50.027Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tomli" +version = "2.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.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typed-ast" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/7e/a424029f350aa8078b75fd0d360a787a273ca753a678d1104c5fa4f3072a/typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd", size = 252841, upload-time = "2023-07-04T18:38:08.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/07/5defe18d4fc16281cd18c4374270abc430c3d852d8ac29b5db6599d45cfe/typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b", size = 223267, upload-time = "2023-07-04T18:37:00.129Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5c/e379b00028680bfcd267d845cf46b60e76d8ac6f7009fd440d6ce030cc92/typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686", size = 208260, upload-time = "2023-07-04T18:37:03.069Z" }, + { url = "https://files.pythonhosted.org/packages/3b/99/5cc31ef4f3c80e1ceb03ed2690c7085571e3fbf119cbd67a111ec0b6622f/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769", size = 842272, upload-time = "2023-07-04T18:37:04.916Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ed/b9b8b794b37b55c9247b1e8d38b0361e8158795c181636d34d6c11b506e7/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04", size = 824651, upload-time = "2023-07-04T18:37:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/ca/59/dbbbe5a0e91c15d14a0896b539a5ed01326b0d468e75c1a33274d128d2d1/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d", size = 854960, upload-time = "2023-07-04T18:37:08.474Z" }, + { url = "https://files.pythonhosted.org/packages/90/f0/0956d925f87bd81f6e0f8cf119eac5e5c8f4da50ca25bb9f5904148d4611/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d", size = 839321, upload-time = "2023-07-04T18:37:10.417Z" }, + { url = "https://files.pythonhosted.org/packages/43/17/4bdece9795da6f3345c4da5667ac64bc25863617f19c28d81f350f515be6/typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02", size = 139380, upload-time = "2023-07-04T18:37:12.157Z" }, + { url = "https://files.pythonhosted.org/packages/75/53/b685e10da535c7b3572735f8bea0d4abb35a04722a7d44ca9c163a0cf822/typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee", size = 223264, upload-time = "2023-07-04T18:37:13.637Z" }, + { url = "https://files.pythonhosted.org/packages/96/fd/fc8ccf19fc16a40a23e7c7802d0abc78c1f38f1abb6e2447c474f8a076d8/typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18", size = 208158, upload-time = "2023-07-04T18:37:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/598e47f2c3ecd19d7f1bb66854d0d3ba23ffd93c846448790a92524b0a8d/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88", size = 878366, upload-time = "2023-07-04T18:37:16.614Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/765e8bf8b24d0ed7b9fc669f6826c5bc3eb7412fc765691f59b83ae195b2/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2", size = 860314, upload-time = "2023-07-04T18:37:18.215Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3c/4af750e6c673a0dd6c7b9f5b5e5ed58ec51a2e4e744081781c664d369dfa/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9", size = 898108, upload-time = "2023-07-04T18:37:20.095Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/d0a4d1e060e1e8dda2408131a0cc7633fc4bc99fca5941dcb86c461dfe01/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8", size = 881971, upload-time = "2023-07-04T18:37:21.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/f28d2c912cd010a09b3677ac69d23181045eb17e358914ab739b7fdee530/typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b", size = 139286, upload-time = "2023-07-04T18:37:23.625Z" }, + { url = "https://files.pythonhosted.org/packages/d5/00/635353c31b71ed307ab020eff6baed9987da59a1b2ba489f885ecbe293b8/typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e", size = 222315, upload-time = "2023-07-04T18:37:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/01/95/11be104446bb20212a741d30d40eab52a9cfc05ea34efa074ff4f7c16983/typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e", size = 793541, upload-time = "2023-07-04T18:37:37.614Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/75bd58fb1410cb72fbc6e8adf163015720db2c38844b46a9149c5ff6bf38/typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311", size = 778348, upload-time = "2023-07-04T18:37:39.332Z" }, + { url = "https://files.pythonhosted.org/packages/47/97/0bb4dba688a58ff9c08e63b39653e4bcaa340ce1bb9c1d58163e5c2c66f1/typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2", size = 809447, upload-time = "2023-07-04T18:37:41.017Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cd/9a867f5a96d83a9742c43914e10d3a2083d8fe894ab9bf60fd467c6c497f/typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4", size = 796707, upload-time = "2023-07-04T18:37:42.625Z" }, + { url = "https://files.pythonhosted.org/packages/eb/06/73ca55ee5303b41d08920de775f02d2a3e1e59430371f5adf7fbb1a21127/typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431", size = 138403, upload-time = "2023-07-04T18:37:44.399Z" }, + { url = "https://files.pythonhosted.org/packages/19/e3/88b65e46643006592f39e0fdef3e29454244a9fdaa52acfb047dc68cae6a/typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a", size = 222951, upload-time = "2023-07-04T18:37:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/15/e0/182bdd9edb6c6a1c068cecaa87f58924a817f2807a0b0d940f578b3328df/typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437", size = 208247, upload-time = "2023-07-04T18:37:47.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/bba083f2c11746288eaf1859e512130420405033de84189375fe65d839ba/typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede", size = 861010, upload-time = "2023-07-04T18:37:48.847Z" }, + { url = "https://files.pythonhosted.org/packages/31/f3/38839df509b04fb54205e388fc04b47627377e0ad628870112086864a441/typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4", size = 840026, upload-time = "2023-07-04T18:37:50.631Z" }, + { url = "https://files.pythonhosted.org/packages/45/1e/aa5f1dae4b92bc665ae9a655787bb2fe007a881fa2866b0408ce548bb24c/typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6", size = 875615, upload-time = "2023-07-04T18:37:52.27Z" }, + { url = "https://files.pythonhosted.org/packages/94/88/71a1c249c01fbbd66f9f28648f8249e737a7fe19056c1a78e7b3b9250eb1/typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4", size = 858320, upload-time = "2023-07-04T18:37:54.23Z" }, + { url = "https://files.pythonhosted.org/packages/12/1e/19f53aad3984e351e6730e4265fde4b949a66c451e10828fdbc4dfb050f1/typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b", size = 139414, upload-time = "2023-07-04T18:37:55.912Z" }, + { url = "https://files.pythonhosted.org/packages/b1/88/6e7f36f5fab6fbf0586a2dd866ac337924b7d4796a4d1b2b04443a864faf/typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10", size = 223329, upload-time = "2023-07-04T18:37:57.344Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/09d27e13824495547bcc665bd07afc593b22b9484f143b27565eae4ccaac/typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814", size = 208314, upload-time = "2023-07-04T18:37:59.073Z" }, + { url = "https://files.pythonhosted.org/packages/07/3d/564308b7a432acb1f5399933cbb1b376a1a64d2544b90f6ba91894674260/typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8", size = 840900, upload-time = "2023-07-04T18:38:00.562Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f4/262512d14f777ea3666a089e2675a9b1500a85b8329a36de85d63433fb0e/typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274", size = 823435, upload-time = "2023-07-04T18:38:02.532Z" }, + { url = "https://files.pythonhosted.org/packages/a1/25/b3ccb948166d309ab75296ac9863ebe2ff209fbc063f1122a2d3979e47c3/typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a", size = 853125, upload-time = "2023-07-04T18:38:04.128Z" }, + { url = "https://files.pythonhosted.org/packages/1c/09/012da182242f168bb5c42284297dcc08dc0a1b3668db5b3852aec467f56f/typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba", size = 837280, upload-time = "2023-07-04T18:38:05.968Z" }, + { url = "https://files.pythonhosted.org/packages/30/bd/c815051404c4293265634d9d3e292f04fcf681d0502a9484c38b8f224d04/typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155", size = 139486, upload-time = "2023-07-04T18:38:07.249Z" }, +] + +[[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.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/af/47/b215df9f71b4fdba1025fc05a77db2ad243fa0926755a52c5e71659f4e3c/urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", size = 282546, upload-time = "2023-10-17T17:46:50.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/b2/b157855192a68541a91ba7b2bbcb91f1b4faa51f8bae38d8005c034be524/urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e", size = 124213, upload-time = "2023-10-17T17:46:48.538Z" }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "watchdog" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/95/a6/d6ef450393dac5734c63c40a131f66808d2e6f59f6165ab38c98fbe4e6ec/watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9", size = 124593, upload-time = "2023-03-20T09:21:11.367Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/fd/58b82550ebe4883bb2a5e1b6c14d8702b5ce0f36c58470bba51dc777df46/watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41", size = 100697, upload-time = "2023-03-20T09:20:25.047Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/42f47ffdfadff4c41b89c54163f323f875eb963bf90088e477c43b8f7b15/watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397", size = 91219, upload-time = "2023-03-20T09:20:26.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/39/30bb3c2e4f8e89b5c60e98589acf5c5a001cb0efde249aa05d748d1734a2/watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96", size = 91756, upload-time = "2023-03-20T09:20:28.309Z" }, + { url = "https://files.pythonhosted.org/packages/00/9e/a9711f35f1ad6571e92dc2e955e7de9dfac21a1b33e9cd212f066a60a387/watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae", size = 100700, upload-time = "2023-03-20T09:20:29.847Z" }, + { url = "https://files.pythonhosted.org/packages/84/ab/67001e62603bf2ea35ace40023f7c74f61e8b047160d6bb078373cec1a67/watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9", size = 91251, upload-time = "2023-03-20T09:20:31.892Z" }, + { url = "https://files.pythonhosted.org/packages/58/db/d419fdbd3051b42b0a8091ddf78f70540b6d9d277a84845f7c5955f9de92/watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7", size = 91753, upload-time = "2023-03-20T09:20:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9d/d6586a065968f3e5d89a2565dffa6ea9151ce9d46c541340bfff27b41231/watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674", size = 91185, upload-time = "2023-03-20T09:20:35.407Z" }, + { url = "https://files.pythonhosted.org/packages/7f/6e/7ca8ed16928d7b11da69372f55c64a09dce649d2b24b03f7063cd8683c4b/watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f", size = 100655, upload-time = "2023-03-20T09:20:37.473Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/48527f3aea4f7ed331072352fee034a7f3d6ec7a2ed873681738b2586498/watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc", size = 91216, upload-time = "2023-03-20T09:20:39.793Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/3a3ce6dd01807ff918aec3bbcabc92ed1a7edc5bb2266c720bb39fec1bec/watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3", size = 91752, upload-time = "2023-03-20T09:20:41.395Z" }, + { url = "https://files.pythonhosted.org/packages/75/fe/d9a37d8df76878853f68dd665ec6d2c7a984645de460164cb880a93ffe6b/watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3", size = 100653, upload-time = "2023-03-20T09:20:42.936Z" }, + { url = "https://files.pythonhosted.org/packages/94/ce/70c65a6c4b0330129c402624d42f67ce82d6a0ba2036de67628aeffda3c1/watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0", size = 91247, upload-time = "2023-03-20T09:20:45.157Z" }, + { url = "https://files.pythonhosted.org/packages/51/b9/444a984b1667013bac41b31b45d9718e069cc7502a43a924896806605d83/watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8", size = 91753, upload-time = "2023-03-20T09:20:46.913Z" }, + { url = "https://files.pythonhosted.org/packages/67/e4/229144d23093436a21a8b84aa5931d70759b81743dc8c10d0e836dbfd752/watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100", size = 90424, upload-time = "2023-03-20T09:20:49.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/bef1c6f6ac18041234a9f3e8bc995d611e255c44f10433bfaf255968c269/watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346", size = 90419, upload-time = "2023-03-20T09:20:50.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/65/9e36a3c821d47a22e54a8fc73681586b2d26e82d24ea3af63acf2ef78f97/watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64", size = 90428, upload-time = "2023-03-20T09:20:52.216Z" }, + { url = "https://files.pythonhosted.org/packages/92/28/631872d7fbc45527037060db8c838b47a129a6c09d2297d6dddcfa283cf2/watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a", size = 82049, upload-time = "2023-03-20T09:20:53.951Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a2/4e3230bdc1fb878b152a2c66aa941732776f4545bd68135d490591d66713/watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44", size = 82049, upload-time = "2023-03-20T09:20:55.583Z" }, + { url = "https://files.pythonhosted.org/packages/21/72/46fd174352cd88b9157ade77e3b8835125d4b1e5186fc7f1e8c44664e029/watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a", size = 82052, upload-time = "2023-03-20T09:20:57.124Z" }, + { url = "https://files.pythonhosted.org/packages/74/3c/e4b77f4f069aca2b6e35925db7a1aa6cb600dcb52fc3e962284640ca37f3/watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709", size = 82050, upload-time = "2023-03-20T09:20:58.864Z" }, + { url = "https://files.pythonhosted.org/packages/71/3a/b12740f4f60861240d57b42a2ac6ac0a2821db506c4435f7872c1fad867d/watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83", size = 82050, upload-time = "2023-03-20T09:21:00.452Z" }, + { url = "https://files.pythonhosted.org/packages/40/1b/4e6d3e0f587587931f590531b4ed08070d71a9efb35541d792a68d8ee593/watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d", size = 82049, upload-time = "2023-03-20T09:21:01.979Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f0/456948b865ab259784f774154e7d65844fa9757522fdb11533fbf8ae7aca/watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33", size = 82051, upload-time = "2023-03-20T09:21:03.67Z" }, + { url = "https://files.pythonhosted.org/packages/55/0d/bfc2a0d425b12444a2dc245a934c065bbb7bd9833fff071cba79c21bb76e/watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f", size = 82038, upload-time = "2023-03-20T09:21:05.492Z" }, + { url = "https://files.pythonhosted.org/packages/9b/6e/ce8d124d03cd3f2941365d9c81d62e3afe43f2dc7e6e86274fa9c2ec2d5b/watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c", size = 82040, upload-time = "2023-03-20T09:21:07.609Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/cd0337069c468f22ef256e768ece74c78b511092f1004ab260268e1af4a9/watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759", size = 82040, upload-time = "2023-03-20T09:21:09.178Z" }, +] + +[[package]] +name = "watchdog" +version = "4.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/38/764baaa25eb5e35c9a043d4c4588f9836edfe52a708950f4b6d5f714fd42/watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270", size = 126587, upload-time = "2024-08-11T07:38:01.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/b0/219893d41c16d74d0793363bf86df07d50357b81f64bba4cb94fe76e7af4/watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22", size = 100257, upload-time = "2024-08-11T07:37:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c6/8e90c65693e87d98310b2e1e5fd7e313266990853b489e85ce8396cc26e3/watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1", size = 92249, upload-time = "2024-08-11T07:37:06.364Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cd/2e306756364a934532ff8388d90eb2dc8bb21fe575cd2b33d791ce05a02f/watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503", size = 92888, upload-time = "2024-08-11T07:37:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/de/78/027ad372d62f97642349a16015394a7680530460b1c70c368c506cb60c09/watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930", size = 100256, upload-time = "2024-08-11T07:37:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/59/a9/412b808568c1814d693b4ff1cec0055dc791780b9dc947807978fab86bc1/watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b", size = 92252, upload-time = "2024-08-11T07:37:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/04/57/179d76076cff264982bc335dd4c7da6d636bd3e9860bbc896a665c3447b6/watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef", size = 92888, upload-time = "2024-08-11T07:37:15.077Z" }, + { url = "https://files.pythonhosted.org/packages/92/f5/ea22b095340545faea37ad9a42353b265ca751f543da3fb43f5d00cdcd21/watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a", size = 100342, upload-time = "2024-08-11T07:37:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d2/8ce97dff5e465db1222951434e3115189ae54a9863aef99c6987890cc9ef/watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29", size = 92306, upload-time = "2024-08-11T07:37:17.997Z" }, + { url = "https://files.pythonhosted.org/packages/49/c4/1aeba2c31b25f79b03b15918155bc8c0b08101054fc727900f1a577d0d54/watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a", size = 92915, upload-time = "2024-08-11T07:37:19.967Z" }, + { url = "https://files.pythonhosted.org/packages/79/63/eb8994a182672c042d85a33507475c50c2ee930577524dd97aea05251527/watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b", size = 100343, upload-time = "2024-08-11T07:37:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/ce/82/027c0c65c2245769580605bcd20a1dc7dfd6c6683c8c4e2ef43920e38d27/watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d", size = 92313, upload-time = "2024-08-11T07:37:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/2a/89/ad4715cbbd3440cb0d336b78970aba243a33a24b1a79d66f8d16b4590d6a/watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7", size = 92919, upload-time = "2024-08-11T07:37:24.715Z" }, + { url = "https://files.pythonhosted.org/packages/55/08/1a9086a3380e8828f65b0c835b86baf29ebb85e5e94a2811a2eb4f889cfd/watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040", size = 100255, upload-time = "2024-08-11T07:37:26.862Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3e/064974628cf305831f3f78264800bd03b3358ec181e3e9380a36ff156b93/watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7", size = 92257, upload-time = "2024-08-11T07:37:28.253Z" }, + { url = "https://files.pythonhosted.org/packages/23/69/1d2ad9c12d93bc1e445baa40db46bc74757f3ffc3a3be592ba8dbc51b6e5/watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4", size = 92886, upload-time = "2024-08-11T07:37:29.52Z" }, + { url = "https://files.pythonhosted.org/packages/68/eb/34d3173eceab490d4d1815ba9a821e10abe1da7a7264a224e30689b1450c/watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9", size = 100254, upload-time = "2024-08-11T07:37:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/18/a1/4bbafe7ace414904c2cc9bd93e472133e8ec11eab0b4625017f0e34caad8/watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578", size = 92249, upload-time = "2024-08-11T07:37:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/f3/11/ec5684e0ca692950826af0de862e5db167523c30c9cbf9b3f4ce7ec9cc05/watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b", size = 92891, upload-time = "2024-08-11T07:37:34.212Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9a/6f30f023324de7bad8a3eb02b0afb06bd0726003a3550e9964321315df5a/watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa", size = 91775, upload-time = "2024-08-11T07:37:35.567Z" }, + { url = "https://files.pythonhosted.org/packages/87/62/8be55e605d378a154037b9ba484e00a5478e627b69c53d0f63e3ef413ba6/watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3", size = 92255, upload-time = "2024-08-11T07:37:37.596Z" }, + { url = "https://files.pythonhosted.org/packages/6b/59/12e03e675d28f450bade6da6bc79ad6616080b317c472b9ae688d2495a03/watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508", size = 91682, upload-time = "2024-08-11T07:37:38.901Z" }, + { url = "https://files.pythonhosted.org/packages/ef/69/241998de9b8e024f5c2fbdf4324ea628b4231925305011ca8b7e1c3329f6/watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee", size = 92249, upload-time = "2024-08-11T07:37:40.143Z" }, + { url = "https://files.pythonhosted.org/packages/70/3f/2173b4d9581bc9b5df4d7f2041b6c58b5e5448407856f68d4be9981000d0/watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1", size = 91773, upload-time = "2024-08-11T07:37:42.095Z" }, + { url = "https://files.pythonhosted.org/packages/f0/de/6fff29161d5789048f06ef24d94d3ddcc25795f347202b7ea503c3356acb/watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e", size = 92250, upload-time = "2024-08-11T07:37:44.052Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b1/25acf6767af6f7e44e0086309825bd8c098e301eed5868dc5350642124b9/watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83", size = 82947, upload-time = "2024-08-11T07:37:45.388Z" }, + { url = "https://files.pythonhosted.org/packages/e8/90/aebac95d6f954bd4901f5d46dcd83d68e682bfd21798fd125a95ae1c9dbf/watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c", size = 82942, upload-time = "2024-08-11T07:37:46.722Z" }, + { url = "https://files.pythonhosted.org/packages/15/3a/a4bd8f3b9381824995787488b9282aff1ed4667e1110f31a87b871ea851c/watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a", size = 82947, upload-time = "2024-08-11T07:37:48.941Z" }, + { url = "https://files.pythonhosted.org/packages/09/cc/238998fc08e292a4a18a852ed8274159019ee7a66be14441325bcd811dfd/watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73", size = 82946, upload-time = "2024-08-11T07:37:50.279Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/d4b915160c9d677174aa5fae4537ae1f5acb23b3745ab0873071ef671f0a/watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc", size = 82947, upload-time = "2024-08-11T07:37:51.55Z" }, + { url = "https://files.pythonhosted.org/packages/db/02/56ebe2cf33b352fe3309588eb03f020d4d1c061563d9858a9216ba004259/watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757", size = 82944, upload-time = "2024-08-11T07:37:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/01/d2/c8931ff840a7e5bd5dcb93f2bb2a1fd18faf8312e9f7f53ff1cf76ecc8ed/watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8", size = 82947, upload-time = "2024-08-11T07:37:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d8/cdb0c21a4a988669d7c210c75c6a2c9a0e16a3b08d9f7e633df0d9a16ad8/watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19", size = 82935, upload-time = "2024-08-11T07:37:56.668Z" }, + { url = "https://files.pythonhosted.org/packages/99/2e/b69dfaae7a83ea64ce36538cc103a3065e12c447963797793d5c0a1d5130/watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b", size = 82934, upload-time = "2024-08-11T07:37:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0b/43b96a9ecdd65ff5545b1b13b687ca486da5c6249475b1a45f24d63a1858/watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c", size = 82933, upload-time = "2024-08-11T07:37:59.573Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, + { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +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" }, +] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From c4842e197a07eea988e148d3964d44840cf2dd1b Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 16:05:43 +0100 Subject: [PATCH 77/78] version 6.2 --- changelog.txt | 12 ++++++++++++ cstruct/__init__.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 8d03ecf..791054c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -198,3 +198,15 @@ ### Fix - fix CStruct.pack() padding + +## [6.2] - 2026-01-10 + +### Added + +- llms.txt added + +### Improved + +- Python 3.14 support +- use ruff instead of flake8/isort/black +- use pyproject.toml instead of setup.cfg diff --git a/cstruct/__init__.py b/cstruct/__init__.py index 149b919..7264908 100644 --- a/cstruct/__init__.py +++ b/cstruct/__init__.py @@ -24,7 +24,7 @@ __author__ = "Andrea Bonomi " __license__ = "MIT" -__version__ = "6.1" +__version__ = "6.2" __date__ = "15 August 2013" from typing import Any, Dict, Optional, Type, Union From d23dfd2b6a674bbf3c66d2461a5c01b2ea8349f9 Mon Sep 17 00:00:00 2001 From: Andrea Bonomi Date: Sat, 10 Jan 2026 16:18:11 +0100 Subject: [PATCH 78/78] fix github release.yml workflow --- .github/workflows/release.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03e2376..fdcc274 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,3 +80,20 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ + + release: + name: Release version + runs-on: ubuntu-latest + needs: [wait, build] + + steps: + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false