From 0795a5f8bb9d1bb81d34b2e1dfb1233e0c572021 Mon Sep 17 00:00:00 2001 From: nikto_b Date: Tue, 26 Aug 2025 17:34:14 +0300 Subject: [PATCH] Add basic inflator cython support --- pyproject.toml | 2 + src/megasniff/__main__.py | 31 ++- src/megasniff/inflator.py | 250 +++++++++++++++++++++++- src/megasniff/templates/inflator.jinja2 | 35 +++- uv.lock | 44 ++++- 5 files changed, 346 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6033938..3d0fc6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,10 @@ authors = [ license = "LGPL-3.0-or-later" requires-python = ">=3.13" dependencies = [ + "cython>=3.1.3", "hatchling>=1.27.0", "jinja2>=3.1.6", + "setuptools>=80.9.0", ] [build-system] diff --git a/src/megasniff/__main__.py b/src/megasniff/__main__.py index 41203a8..019a049 100644 --- a/src/megasniff/__main__.py +++ b/src/megasniff/__main__.py @@ -5,18 +5,27 @@ from typing import Optional from typing import TypedDict import megasniff.exceptions +from megasniff.python_to_cython import python_obj_to_cython from . import SchemaInflatorGenerator -@dataclass(frozen=True) +@dataclass class ASchema: - a: int + a: int | None b: float | str bs: Optional[BSchema] + d: int c: float = 1.1 + def __init__(self, a: int | None, b: float | str, bs: Optional[BSchema], c: float = 1.1): + self.a = a + self.b = b + self.bs = bs + self.c = c + self.d = a or 0 -class BSchema(TypedDict): +@dataclass +class BSchema: a: int b: str c: float @@ -27,10 +36,23 @@ class BSchema(TypedDict): class CSchema: l: set[int | ASchema] +@dataclass +class SomeData: + a: int + b: float + c: str def main(): + # ccode = python_obj_to_cython(ASchema) + # print(ccode) + # exit(0) + + # infl = SchemaInflatorGenerator(strict_mode=True) + # fn = infl.schema_to_inflator(SomeData) + # print(fn({'a': 1, 'b': 1.1, 'c': 'asdf'})) infl = SchemaInflatorGenerator(strict_mode=True) fn = infl.schema_to_inflator(ASchema) + # exit(0) # print(t) # print(n) # exec(t, n) @@ -40,7 +62,8 @@ def main(): # d = {'a': 1, 'b': 1, 'c': 0, 'bs': {'a': 1, 'b': 2, 'c': 3, 'd': {'a': 1, 'b': 2.1, 'bs': None}}} # d = {'a': 2, 'b': 2, 'bs': {'a': 2, 'b': 'a', 'c': 0, 'd': {'a': 2, 'b': 2}}} # d = {'l': ['1', {'a': 42, 'b': 1}]} - d = {'a': 2, 'b': '2', 'bs': None} + # d = {'a': None, 'b': '2', 'bs': None} + d = {'a': None, 'b': '2', 'bs': {'a': 1, 'b': 'a', 'c': 1.1, 'd': {'a': 1, 'b': '', 'bs': None}}} try: o = fn(d) print(o) diff --git a/src/megasniff/inflator.py b/src/megasniff/inflator.py index 49fb6f6..03f161a 100644 --- a/src/megasniff/inflator.py +++ b/src/megasniff/inflator.py @@ -2,11 +2,18 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from __future__ import annotations import collections.abc +import hashlib import importlib.resources +import importlib.util +import os +import subprocess +import sys +import tempfile from collections import defaultdict from collections.abc import Callable from dataclasses import dataclass -from types import NoneType, UnionType +from pathlib import Path +from types import NoneType, UnionType, ModuleType from typing import Optional, get_origin, get_args, Union, Annotated, Literal, Sequence, List, Set, TypeAliasType, \ OrderedDict @@ -14,6 +21,7 @@ import jinja2 from . import utils from .utils import * +import random, string @dataclass @@ -23,6 +31,7 @@ class TypeRenderData: is_list: bool is_union: bool is_strict: bool + ctype: str @dataclass @@ -48,6 +57,7 @@ class FieldRenderData: is_optional: bool allow_none: bool default_option: Optional[str] + ctype: str def __init__(self, argname: str, @@ -55,7 +65,8 @@ class FieldRenderData: typename: str, is_optional: bool, allow_none: bool, - default_option: Optional[str]): + default_option: Optional[str], + ctype: str): self.argname = argname self.constrs = constrs self.typename = typename @@ -63,6 +74,221 @@ class FieldRenderData: self.allow_none = allow_none self.default_option = default_option self.argname_escaped = _escape_python_name(argname) + self.ctype = ctype + + +def randomword(length): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + + +def exec_cython(txt: str, namespace: dict, name: str): + """ + Drop-in замена exec(txt, namespace), но через cython. + Возвращает callable объект из namespace['inflator']. + """ + # генерируем уникальное имя для модуля + h = hashlib.sha256(txt.encode() + str(sorted(namespace.keys())).encode()).hexdigest()[:16] + modname = f"_cyexec_{h}" + + build_dir = tempfile.mkdtemp(prefix="cyexec_") + pyx_file = os.path.join(build_dir, f"{modname}.pyx") + + # соберём код для .pyx + # сначала экспортируем namespace + export_lines = [] + for k, v in namespace.items(): + if k not in {'int', 'float', 'str'}: + export_lines.append(f"{k} = __ns__['{k}']") + + pyx_code = f""" +# cython: language_level=3 +# cython: boundscheck=False, wraparound=False, nonecheck=False +# AUTO-GENERATED + +# Вставляем runtime namespace +import builtins +__ns__ = builtins.__dict__['_cyexec_ns'] + +cdef class NullableInt: + cdef long value + cdef bint has + + cpdef set(self, long value): + self.value = value + self.has = 1 + + cpdef unset(self): + self.has = 0 + +cdef class NullableDouble: + cdef double value + cdef bint has + + cpdef set(self, double value): + self.value = value + self.has = 1 + + cpdef unset(self): + self.has = 0 +{os.linesep.join(export_lines)} + + + + +# пользовательский код +{txt} +""" + + # пишем файл + with open(pyx_file, "w") as f: + f.write(pyx_code) + + # нужно сохранить namespace в builtins, чтобы cython его видел + import builtins + builtins._cyexec_ns = namespace + + # компилируем через cythonize + setup_code = f""" +from setuptools import setup +from Cython.Build import cythonize +setup( + name="{modname}", + ext_modules=cythonize("{pyx_file}", compiler_directives={{"language_level": "3"}}), + script_args=["build_ext", "--inplace"], +) +""" + setup_file = os.path.join(build_dir, "setup.py") + with open(setup_file, "w") as f: + f.write(setup_code) + + subprocess.check_call([sys.executable, setup_file, "build_ext", "--inplace"], cwd=build_dir) + + # находим .so файл + for fn in os.listdir(build_dir): + if fn.startswith(modname) and fn.endswith((".so", ".pyd")): + so_path = os.path.join(build_dir, fn) + break + else: + raise RuntimeError("Cython build failed, no .so produced") + + # импортим как модуль + spec = importlib.util.spec_from_file_location(modname, so_path) + mod = importlib.util.module_from_spec(spec) + sys.modules[modname] = mod + spec.loader.exec_module(mod) + + # чистим временный namespace в builtins + del builtins._cyexec_ns + + return getattr(mod, name) + + +@dataclass +class BasicTypeVariationTest: + index: int + basic_type: str + + +@dataclass +class ObjectTypeVariationTest: + index: int + fields_contains: list[tuple[str, TypeVariationTest | None]] + + +@dataclass +class TypeVariationTest: + types: list[TypeConstructionSchema] + basic_tests: list[BasicTypeVariationTest] + object_tests: list[ObjectTypeVariationTest] + + +@dataclass +class TypeConstructionSchema: + tp: type + allow_none: bool + kwargs: Optional[dict[str, tuple[str, list[TypeConstructionSchema] | type]]] + + @property + def typed_key_pairs(self) -> set[str]: + ret = set() + for k, (_, v) in self.kwargs.items(): + if isinstance(v, type): + ret.add(f'{k}:{v}') + else: + if not isinstance(v, list): + v = [v] + for _v in v: + ret.add(f'{k}:{_v.tp}') + + return ret + + +class Z1: + a: int + b: int + + +class Z2: + c: int + d: int + + +class Z3: + e: int + f: int + + +class A: + a: int + b: Z1 | Z3 + + +class B: + a: int + b: Z2 + + +class C: + z1: Z1 + z2: Z2 + + +_ = A | B | C + + +def find_types_variations(types: list[TypeConstructionSchema]) -> TypeVariationTest: + basic_tests = [] + object_tests = [] + for i, tp in enumerate(types): + if tp.kwargs is None: + basic_tests.append(BasicTypeVariationTest(i, _type_to_ctype(tp.tp, tp.allow_none))) + for i, tp in enumerate(types): + if tp.kwargs is not None: + tp_keys = [] + uniq_keys = set() + if i < len(types): + keys = set() + for t in types[i + 1:]: + keys |= set((t.kwargs or {}).keys()) + obj_keys = set(tp.kwargs.keys()) + uniq_keys = (obj_keys ^ keys) & obj_keys + + if len(uniq_keys) > 0: + k = list(uniq_keys)[0] + object_tests.append(ObjectTypeVariationTest(i, [(k, None)])) + else: + pass + + return TypeVariationTest(types, basic_tests, object_tests) + + +def _type_to_ctype(t: type, allow_none: bool) -> str: + if t is int: + return 'NullableInt' if allow_none else 'long' + if t is float: + return 'NullableFloat' if allow_none else 'double' + return 'object' class SchemaInflatorGenerator: @@ -100,18 +326,23 @@ class SchemaInflatorGenerator: strict_mode_override: Optional[bool] = None, from_type_override: Optional[type | TypeAliasType] = None ) -> Callable[[dict[str, Any]], Any]: + name = 'inflate' + if isinstance(schema, type): + name = f'inflate_{schema.__name__}' if from_type_override is not None and '__getitem__' not in dir(from_type_override): raise RuntimeError('from_type_override must provide __getitem__') txt, namespace = self._schema_to_inflator(schema, - _funcname='inflate', + _funcname=name, strict_mode_override=strict_mode_override, from_type_override=from_type_override, ) imports = ('from typing import Any\n' 'from megasniff.exceptions import MissingFieldException, FieldValidationException\n') txt = imports + '\n' + txt - exec(txt, namespace) - fn = namespace['inflate'] + fn = exec_cython(txt, namespace, name) + # fn = exec_numba(txt, namespace, func_name=name) + # exec(txt, namespace) + # fn = namespace[name] if self._store_sources: setattr(fn, '__megasniff_sources__', txt) return fn @@ -132,10 +363,11 @@ class SchemaInflatorGenerator: if is_union: typerefs = list(map(lambda x: self._unwrap_typeref(x, strict_mode), argtypes)) - return TypeRenderData(typerefs, allow_none, False, True, False) + return TypeRenderData(typerefs, allow_none, False, True, False, 'object') elif type_origin in [list, set]: rd = self._unwrap_typeref(argtypes[0], strict_mode) - return IterableTypeRenderData(rd, allow_none, True, False, False, type_origin.__name__) + return IterableTypeRenderData(rd, allow_none, True, False, False, type_origin.__name__, + 'NullableList' if allow_none else 'list') else: t = argtypes[0] @@ -148,7 +380,8 @@ class SchemaInflatorGenerator: allow_none, is_list, False, - strict_mode if is_builtin else False) + strict_mode if is_builtin else False, + _type_to_ctype(t, allow_none)) def _schema_to_inflator(self, schema: type | Sequence[TupleSchemaItem | tuple[str, type]] | OrderedDict[str, type], @@ -243,6 +476,7 @@ class SchemaInflatorGenerator: has_default, allow_none, default_option if not isinstance(default_option, str) else f"'{default_option}'", + typeref.ctype ) ) diff --git a/src/megasniff/templates/inflator.jinja2 b/src/megasniff/templates/inflator.jinja2 index 43d6e9f..be2f77b 100644 --- a/src/megasniff/templates/inflator.jinja2 +++ b/src/megasniff/templates/inflator.jinja2 @@ -2,16 +2,45 @@ {% import "unwrap_type_data.jinja2" as unwrap_type_data %} -def {{funcname}}(from_data: {% if from_type is none %}dict[str, Any]{% else %}{{from_type}}{% endif %}) {% if tgt_type is not none %} -> {{tgt_type}} {% endif %}: +{% macro render_setter(argname, argval) -%} +{%- set out -%} +{% if argname.startswith('Nullable') %} +{% if argval == 'None' %} +{{argname}}.unset() +{% else %} +{{argname}}.set({{argval}}) +{%endif%} +{% else %} +{{argname}} = {{argval}} +{% endif %} +{%- endset %} +{{out}} +{%- endmacro %} + +{% macro check_null(argname) -%} +{%- set out -%} +{% if argname.startswith('Nullable') %} +if {{argname}}.has: +{% else %} +if {{argname}} is None: +{% endif %} +{%- endset %} +{{out}} +{%- endmacro %} + + +cpdef object {{funcname}}(dict from_data): """ {{tgt_type}} """ from_data_keys = from_data.keys() + cdef object conv_data {% for conv in conversions %} + cdef {{conv.argctype}} {{conv.argname_escaped}} if '{{conv.argname}}' not in from_data_keys: {% if conv.is_optional %} - {{conv.argname_escaped}} = {{conv.default_option}} +{{ render_setter(conv.argname_escaped, conv.default_option) | indent(4*2) }} {% else %} raise MissingFieldException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}") {% endif %} @@ -21,7 +50,7 @@ def {{funcname}}(from_data: {% if from_type is none %}dict[str, Any]{% else %}{{ {% if not conv.allow_none %} raise FieldValidationException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}", conv_data) {% else %} - {{conv.argname_escaped}} = None + {{ render_setter(conv.argname_escaped, 'None') | indent(4*3) }} {% endif %} else: diff --git a/uv.lock b/uv.lock index 6fc8a33..4c43904 100644 --- a/uv.lock +++ b/uv.lock @@ -42,6 +42,35 @@ wheels = [ { 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 = "cython" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/ab/915337fb39ab4f4539a313df38fc69938df3bf14141b90d61dfd5c2919de/cython-3.1.3.tar.gz", hash = "sha256:10ee785e42328924b78f75a74f66a813cb956b4a9bc91c44816d089d5934c089", size = 3186689, upload-time = "2025-08-13T06:19:13.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/93/0e5dfcc6215a6c2cae509d7e40f8fb197237ba5998c936e9c19692f8eedf/cython-3.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9458d540ef0853ea4fc65b8a946587bd483ef7244b470b3d93424eb7b04edeb1", size = 2998232, upload-time = "2025-08-13T06:20:35.817Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6c/01b22de45e3a9b86fbe4a18cd470146514209448cb4d3d3ba9c72390d45b/cython-3.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:32d1b22c3b231326e9f16480a7f508c6841bbf7d0615c2d6f489ebc72dd05205", size = 2830052, upload-time = "2025-08-13T06:20:37.71Z" }, + { url = "https://files.pythonhosted.org/packages/52/08/a7d4b91b144b4bd015e932303861061cd43221f737ecdc6e380a438f245f/cython-3.1.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4c7e0b8584b02a349952de7d7d47f89c97cbf3fee74962e89e3caa78139ec84", size = 3359478, upload-time = "2025-08-13T06:20:39.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7d/b44ee735439ee73a88c6532536cfbc5b2f146c5f315effa124e85aadb447/cython-3.1.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9178f0c06f4bc92372dc44e3867e9285bebd556953e47857c26b389aabe2828", size = 3155157, upload-time = "2025-08-13T06:20:42.305Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e0/ef1a44ba765057b04e99cf34dcc1910706a666ea66fcd2b92175ab645416/cython-3.1.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4da2e624d381e9790152672bfc599a5fb4b823b99d82700a10f5db3311851f9", size = 3305331, upload-time = "2025-08-13T06:20:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/62/f1/8bf3ea5babdef82df3023e72522c71bfc5cc5091e9710828a0dda81bda88/cython-3.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:680c9168882c5e8031dd31df199b9a5ee897e95136d15f8c6454b62162ede25e", size = 3171968, upload-time = "2025-08-13T06:20:48.962Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c3/c1383f987d3add9cb8655943f6a0f164bfd06951f28e51b7887d12c8716a/cython-3.1.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:833cd0fdba9210d2f1f29e097579565a296d7ff567fd63e8cf5fde4c14339f4f", size = 3372840, upload-time = "2025-08-13T06:20:51.495Z" }, + { url = "https://files.pythonhosted.org/packages/71/d5/02fb7454756cb31b0c044050ee563ac172314aa8e74e5a4dd73bf77041d3/cython-3.1.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c04367fa0e6c35b199eb51d64b5e185584b810f6c2b96726ce450300faf99686", size = 3317912, upload-time = "2025-08-13T06:20:53.461Z" }, + { url = "https://files.pythonhosted.org/packages/91/62/b96227adf45236952f7cf07f869ff4157b82fe25ff7bb5ba9a3037c98993/cython-3.1.3-cp313-cp313-win32.whl", hash = "sha256:f02ef2bf72a576bf541534c704971b8901616db431bc46d368eed1d6b20aaa1e", size = 2479889, upload-time = "2025-08-13T06:20:55.437Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/100c0727d0fc8e4d7134c44c12b8c623e40f309401af56b7f6faf795c4bb/cython-3.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:00264cafcc451dcefc01eaf29ed5ec150fb73af21d4d21105d97e9d829a53e99", size = 2701550, upload-time = "2025-08-13T06:20:57.503Z" }, + { url = "https://files.pythonhosted.org/packages/23/0e/6e535f2eedf0ddc3c84b087e5d0f04a7b88d8229ec8c27be41a142bcbbfa/cython-3.1.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62b0a9514b68391aae9784405b65738bbe19cdead3dd7b90dd9e963281db1ee3", size = 2995613, upload-time = "2025-08-13T06:20:59.408Z" }, + { url = "https://files.pythonhosted.org/packages/77/10/3c9e2abf315f608bc22f49b6f9ee66859c23e07edbf484522d5f27b61ab7/cython-3.1.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:976db373c315f342dcb24cd65b5e4c08d2c7b42f9f6ac1b3f677eb2abc9bfb0f", size = 2841282, upload-time = "2025-08-13T06:21:01.274Z" }, + { url = "https://files.pythonhosted.org/packages/cd/77/04e39af308d5716640bc638e7d90d8be34277ebc642ea5bda5ac09628215/cython-3.1.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e765c12a02dea0bd968cf1e85af77be1dc6d21909c3fbf5bd81815a7cdd4a65e", size = 3361624, upload-time = "2025-08-13T06:21:03.418Z" }, + { url = "https://files.pythonhosted.org/packages/75/f4/bdbc989ad88401e03ffe17e0bc3a03e3fe5dccbeb9c90e8762d7da4c7a45/cython-3.1.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:097374fa1370e9967e48442a41a0acbebb94fe9d63976cad31eacd38424847bf", size = 3194014, upload-time = "2025-08-13T06:21:05.719Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/9f282e5d31280f3912199b638c71557062443608eb3909a562283eda376d/cython-3.1.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d8fda4d62b693e62992c665a688e3a220be70958c48eb4c2634093c9998156", size = 3309703, upload-time = "2025-08-13T06:21:08.026Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/83416a454a575e3ea7e84ec138f0b6dbfb34de28de4968359d7fdb428028/cython-3.1.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:da23fa5082940ae1eed487ee9b7c1da7015b53f9feffeee661f4ee57f696dcd5", size = 3210317, upload-time = "2025-08-13T06:21:10.92Z" }, + { url = "https://files.pythonhosted.org/packages/8f/dc/901ed74302d52105588c59a41a239ef6bd01ff708391a15938aba9670b9e/cython-3.1.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8880daa7a0ddf971593f24da161c976bc1bea895393fdfebb8e54269321d9d2b", size = 3378211, upload-time = "2025-08-13T06:21:13.067Z" }, + { url = "https://files.pythonhosted.org/packages/b7/6d/1e077b99a678b69a39bfe96e1888bcf6c868830220e635f862a44c7761b4/cython-3.1.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20d6b5a9fc210d3bc2880413011f606e1208e12ee6efc74717445a63f9795af1", size = 3321051, upload-time = "2025-08-13T06:21:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/00/cd/2c442e9e41eafa851d89af1f62720007e03a12e1c01d9a71ed75f550a6c5/cython-3.1.3-cp314-cp314-win32.whl", hash = "sha256:3b2243fed3eeb129dedf2cebbe3be0d9b02fbf3bc75b387aafd54aac3950baa6", size = 2502067, upload-time = "2025-08-13T06:21:19.404Z" }, + { url = "https://files.pythonhosted.org/packages/ae/63/7a1f2f06331f7dcf3fd31721fdaa8b60762748b82395631c0324672a4f2b/cython-3.1.3-cp314-cp314-win_amd64.whl", hash = "sha256:d32792c80b1fa8be9de207ec8844d49c4d1d0d60e5136d20f344729270db6490", size = 2733427, upload-time = "2025-08-13T06:21:21.525Z" }, + { url = "https://files.pythonhosted.org/packages/56/c8/46ac27096684f33e27dab749ef43c6b0119c6a0d852971eaefb73256dc4c/cython-3.1.3-py3-none-any.whl", hash = "sha256:d13025b34f72f77bf7f65c1cd628914763e6c285f4deb934314c922b91e6be5a", size = 1225725, upload-time = "2025-08-13T06:19:09.593Z" }, +] + [[package]] name = "hatchling" version = "1.27.0" @@ -108,11 +137,13 @@ wheels = [ [[package]] name = "megasniff" -version = "0.1.2" +version = "0.2.3.post2" source = { editable = "." } dependencies = [ + { name = "cython" }, { name = "hatchling" }, { name = "jinja2" }, + { name = "setuptools" }, ] [package.dev-dependencies] @@ -123,8 +154,10 @@ dev = [ [package.metadata] requires-dist = [ + { name = "cython", specifier = ">=3.1.3" }, { name = "hatchling", specifier = ">=1.27.0" }, { name = "jinja2", specifier = ">=3.1.6" }, + { name = "setuptools", specifier = ">=80.9.0" }, ] [package.metadata.requires-dev] @@ -199,6 +232,15 @@ 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 = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + [[package]] name = "trove-classifiers" version = "2025.5.9.12"