diff --git a/pyproject.toml b/pyproject.toml index a95b28c..c4a2f9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,3 +18,9 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/megasniff"] + +[dependency-groups] +dev = [ + "pytest>=8.4.1", + "pytest-cov>=6.2.1", +] diff --git a/src/megasniff/__pycache__/__init__.cpython-313.pyc b/src/megasniff/__pycache__/__init__.cpython-313.pyc index ecf63c7..058ef70 100644 Binary files a/src/megasniff/__pycache__/__init__.cpython-313.pyc and b/src/megasniff/__pycache__/__init__.cpython-313.pyc differ diff --git a/src/megasniff/__pycache__/utils.cpython-313.pyc b/src/megasniff/__pycache__/utils.cpython-313.pyc index 05c8cab..57ac6b9 100644 Binary files a/src/megasniff/__pycache__/utils.cpython-313.pyc and b/src/megasniff/__pycache__/utils.cpython-313.pyc differ diff --git a/src/megasniff/inflator.py b/src/megasniff/inflator.py index b181f2e..3c3fbd8 100644 --- a/src/megasniff/inflator.py +++ b/src/megasniff/inflator.py @@ -19,6 +19,7 @@ class RenderData: typename: str is_union: bool is_optional: bool + allow_none: bool default_option: Optional[str] @@ -56,13 +57,14 @@ class SchemaInflatorGenerator: has_default, default_option = get_field_default(schema, argname) argtypes = argtype, type_origin = get_origin(argtype) + allow_none = False if any(map(lambda x: type_origin is x, [Union, UnionType, Optional, Annotated])): argtypes = get_args(argtype) if NoneType in argtypes or None in argtypes: argtypes = tuple(filter(lambda x: x is not None and x is not NoneType, argtypes)) - has_default = True + allow_none = True out_argtypes: list[tuple[str, bool]] = [] @@ -72,13 +74,23 @@ class SchemaInflatorGenerator: if argt.__name__ not in lookup_table.keys(): # если случилась циклическая зависимость, мы не хотим бексконечную рекурсию lookup_table[argt.__name__] = self.schema_to_generator(argt, _base_lookup_table=lookup_table) + if argt is schema: out_argtypes.append(('inflate', True)) else: out_argtypes.append((argt.__name__, is_builtin)) render_data.append( - RenderData(argname, out_argtypes, repr(argtype), len(argtypes) > 1, has_default, default_option)) + RenderData( + argname, + out_argtypes, + repr(argtype), + len(argtypes) > 1, + has_default, + allow_none, + default_option + ) + ) convertor_functext = self.template.render(conversions=render_data) convertor_functext = '\n'.join(list(filter(lambda x: len(x.strip()), convertor_functext.split('\n')))) @@ -88,6 +100,7 @@ class SchemaInflatorGenerator: '_lookup_table': lookup_table } exec(convertor_functext, namespace) + print(convertor_functext) # пихаем сгенеренный метод в табличку, # ожидаем что она обновится во всех вложенных методах, diff --git a/src/megasniff/templates/inflator.jinja2 b/src/megasniff/templates/inflator.jinja2 index 62feaf8..b3ac531 100644 --- a/src/megasniff/templates/inflator.jinja2 +++ b/src/megasniff/templates/inflator.jinja2 @@ -1,7 +1,7 @@ {% set ns = namespace(retry_indent=0) %} from typing import Any def inflate(from_data: dict[str, Any]): - from_data_keys = set(from_data.keys()) + from_data_keys = from_data.keys() {% for conv in conversions %} {% if not conv.is_optional or conv.default_option is not none%} @@ -16,41 +16,50 @@ def inflate(from_data: dict[str, Any]): {%endfor%} {% for conv in conversions %} - {% if conv.is_union %} - {% set ns.retry_indent = 0 %} - {% for union_type, is_builtin in conv.constrs %} - {{ ' ' * ns.retry_indent }}try: - {% if is_builtin %} - {{ ' ' * ns.retry_indent }} {{conv.argname}} = {{union_type}}(from_data['{{conv.argname}}']) + conv_data = from_data['{{conv.argname}}'] + if conv_data is None: + {% if not conv.allow_none %} + raise ValueError(f"Field {{conv.argname}} required type {{conv.typename | replace('"', "'")}}, null provided") {% else %} - {{ ' ' * ns.retry_indent }} {{conv.argname}} = _lookup_table['{{union_type}}'](from_data['{{conv.argname}}']) - {% endif %} - {{ ' ' * ns.retry_indent }}except Exception as e: - {% set ns.retry_indent = ns.retry_indent + 1 %} - {% endfor %} - {{ ' ' * ns.retry_indent }} raise e from e - {% else %} - - {% if conv.constrs[0][1] %} - {% if conv.is_optional %} - if '{{conv.argname}}' not in from_data_keys: {{conv.argname}} = None + {% endif %} else: - {{conv.argname}} = {{conv.constrs[0][0]}}(from_data['{{conv.argname}}']) - {% else %} - {{conv.argname}} = {{conv.constrs[0][0]}}(from_data['{{conv.argname}}']) - {% endif %} - {% else %} - {% if conv.is_optional %} - if '{{conv.argname}}' not in from_data_keys: - {{conv.argname}} = None - else: - {{conv.argname}} = _lookup_table['{{conv.constrs[0][0]}}'](from_data['{{conv.argname}}']) - {% else %} - {{conv.argname}} = _lookup_table['{{conv.constrs[0][0]}}'](from_data['{{conv.argname}}']) - {% endif %} - {% endif %} - {% endif %} + {% if conv.is_union %} + {% set ns.retry_indent = 0 %} + {% for union_type, is_builtin in conv.constrs %} + {{ ' ' * ns.retry_indent }}try: + {% if is_builtin %} + {{ ' ' * ns.retry_indent }} {{conv.argname}} = {{union_type}}(conv_data) + {% else %} + {{ ' ' * ns.retry_indent }} {{conv.argname}} = _lookup_table['{{union_type}}'](conv_data) + {% endif %} + {{ ' ' * ns.retry_indent }}except Exception as e: + {% set ns.retry_indent = ns.retry_indent + 1 %} + {% endfor %} + {{ ' ' * ns.retry_indent }} raise e from e + {% else %} + + {% if conv.constrs[0][1] %} + {% if conv.is_optional %} + if '{{conv.argname}}' not in from_data_keys: + {{conv.argname}} = None + else: + {{conv.argname}} = {{conv.constrs[0][0]}}(conv_data) + {% else %} + {{conv.argname}} = {{conv.constrs[0][0]}}(conv_data) + {% endif %} + {% else %} + {% if conv.is_optional %} + if '{{conv.argname}}' not in from_data_keys: + {{conv.argname}} = None + else: + {{conv.argname}} = _lookup_table['{{conv.constrs[0][0]}}'](conv_data) + {% else %} + {{conv.argname}} = _lookup_table['{{conv.constrs[0][0]}}'](conv_data) + {% endif %} + {% endif %} + + {% endif %} {% endfor %} return _tgt_type({% for conv in conversions %}{{conv.argname}}={{conv.argname}}, {% endfor %}) diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..61eff88 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,34 @@ +from __future__ import annotations +from dataclasses import dataclass + +from src.megasniff import SchemaInflatorGenerator + + +def test_basic_constructor(): + class A: + def __init__(self, a: int): + self.a = a + + infl = SchemaInflatorGenerator() + fn = infl.schema_to_generator(A) + a = fn({'a': 42}) + + assert a.a == 42 + + +@dataclass +class CircA: + b: CircB + + +@dataclass +class CircB: + a: CircA | None + + +def test_circular(): + infl = SchemaInflatorGenerator() + fn = infl.schema_to_generator(CircA) + a = fn({'b': {'a': None}}) + + return isinstance(a.b, CircB) diff --git a/uv.lock b/uv.lock index 2d062f7..6fc8a33 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,46 @@ version = 1 revision = 2 requires-python = ">=3.13" +[[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.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload-time = "2025-07-03T10:54:15.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload-time = "2025-07-03T10:53:25.811Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload-time = "2025-07-03T10:53:27.075Z" }, + { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload-time = "2025-07-03T10:53:28.408Z" }, + { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload-time = "2025-07-03T10:53:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload-time = "2025-07-03T10:53:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload-time = "2025-07-03T10:53:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload-time = "2025-07-03T10:53:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload-time = "2025-07-03T10:53:35.434Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload-time = "2025-07-03T10:53:36.787Z" }, + { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload-time = "2025-07-03T10:53:38.188Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload-time = "2025-07-03T10:53:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload-time = "2025-07-03T10:53:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload-time = "2025-07-03T10:53:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload-time = "2025-07-03T10:53:43.823Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload-time = "2025-07-03T10:53:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload-time = "2025-07-03T10:53:46.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload-time = "2025-07-03T10:53:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload-time = "2025-07-03T10:53:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload-time = "2025-07-03T10:53:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload-time = "2025-07-03T10:53:52.808Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload-time = "2025-07-03T10:53:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload-time = "2025-07-03T10:53:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, +] + [[package]] name = "hatchling" version = "1.27.0" @@ -17,6 +57,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794, upload-time = "2024-12-15T17:08:10.364Z" }, ] +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -59,19 +108,31 @@ wheels = [ [[package]] name = "megasniff" -version = "0.1.0" +version = "0.1.2" source = { editable = "." } dependencies = [ { name = "hatchling" }, { name = "jinja2" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + [package.metadata] requires-dist = [ { name = "hatchling", specifier = ">=1.27.0" }, { name = "jinja2", specifier = ">=3.1.6" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-cov", specifier = ">=6.2.1" }, +] + [[package]] name = "packaging" version = "25.0" @@ -99,6 +160,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + [[package]] name = "trove-classifiers" version = "2025.5.9.12"