From 5dfb3ad7282f2cb61ced26225ce4f57aeca3cf0b Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 14:56:33 +0200 Subject: [PATCH 01/12] feat: add .gitignore file for Python and tooling caches --- py/.gitignore | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 py/.gitignore diff --git a/py/.gitignore b/py/.gitignore new file mode 100644 index 0000000..78c66d0 --- /dev/null +++ b/py/.gitignore @@ -0,0 +1,16 @@ +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +.eggs/ +build/ +dist/ +.venv/ +venv/ + +# Tooling caches +.pytest_cache/ +.mypy_cache/ + +# Node helper (hercules/_tsgen): keep sources + lockfile, ignore installed deps +hercules/_tsgen/node_modules/ From 4767cb92b2c88f7477724da3fda93af713eca2da Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 14:56:43 +0200 Subject: [PATCH 02/12] feat: add Python version and pyproject.toml for SDK configuration --- py/.python-version | 1 + py/pyproject.toml | 54 +++ py/uv.lock | 813 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 868 insertions(+) create mode 100644 py/.python-version create mode 100644 py/pyproject.toml create mode 100644 py/uv.lock diff --git a/py/.python-version b/py/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/py/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/py/pyproject.toml b/py/pyproject.toml new file mode 100644 index 0000000..b03cc82 --- /dev/null +++ b/py/pyproject.toml @@ -0,0 +1,54 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "code0-hercules" +version = "0.0.0" +description = "Python SDK for the hercules action runner" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "ISC" } +dependencies = [ + "grpcio>=1.64", + "protobuf>=5.0", + "tucana>=0.0.80", + "pydantic>=2", +] + +[project.optional-dependencies] +# TypeScript <-> Pydantic schema conversion. ``datamodel-code-generator`` turns +# a JSON schema into Pydantic code; the reverse (Pydantic -> TypeScript) and the +# TypeScript -> JSON-schema step both shell out to the bundled Node helper in +# ``hercules/_tsgen`` (see its package.json), so Node must be available. +codegen = [ + "datamodel-code-generator>=0.25", +] + +# Development dependencies, installed by default on ``uv sync``. +[dependency-groups] +dev = [ + "datamodel-code-generator>=0.25", + "pytest>=7.4", + "pytest-asyncio>=0.23", +] + +[project.scripts] +hercules = "hercules.cli:main" + +[project.urls] +Repository = "https://github.com/code0-tech/hercules" + +[tool.setuptools.packages.find] +include = ["hercules*"] +# ``_tsgen`` is the bundled Node helper: it ships as data files (below), not as an +# importable Python package. Excluding it also stops setuptools from recursing into +# ``_tsgen/node_modules`` and registering thousands of stray namespace packages, +# which otherwise bloats the editable-install finder and breaks IDE resolution. +exclude = ["hercules._tsgen*"] + +[tool.setuptools.package-data] +hercules = ["_tsgen/*.mjs", "_tsgen/package.json", "_tsgen/package-lock.json"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/py/uv.lock b/py/uv.lock new file mode 100644 index 0000000..a6a7fed --- /dev/null +++ b/py/uv.lock @@ -0,0 +1,813 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/6f/5a73f04007ca950701765949209f068da628bd11f9c2da287278ce91e0ee/argcomplete-3.7.2.tar.gz", hash = "sha256:aad8b69a0b9969edb62db0d1752354c0d50717b10e0cbb00e2a958381b9fc6b9", size = 74473, upload-time = "2026-08-06T04:53:21.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/bd/551ee6af426af84ca33e02622be722925c196608e9127d731ef17c47f06e/argcomplete-3.7.2-py3-none-any.whl", hash = "sha256:6029205678bdd9c1c728a155f5f9ecf5812393f969eef58807641a2bc2aa5b19", size = 43294, upload-time = "2026-08-06T04:53:20.246Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "black" +version = "26.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/84/b3f55026206a9e8820a91503308075ca48eadc515e436731ca01dbe043b3/black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", size = 1987719, upload-time = "2026-05-18T17:05:02.757Z" }, + { url = "https://files.pythonhosted.org/packages/c6/34/7db312c5e5783d6e76cffd9d5ac8972a32badae4c6e3288dac0eed8d3bed/black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", size = 1810083, upload-time = "2026-05-18T17:05:04.302Z" }, + { url = "https://files.pythonhosted.org/packages/33/e2/e0101e73c2c8727634e2efcb35e2b34bd23ad70dfa673789f5773a591b21/black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", size = 1860633, upload-time = "2026-05-18T17:05:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/e15c0c5b23cf3651035fe5addcce90e283af3548a3f91bb03d81b83106ab/black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", size = 1477886, upload-time = "2026-05-18T17:05:07.96Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3f/59d43ade98d2ce5c8dc34a4e46cbecd177e6d55d7d4092969c6003ccc655/black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", size = 1277111, upload-time = "2026-05-18T17:05:09.473Z" }, + { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "code0-hercules" +version = "0.0.0" +source = { editable = "." } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "tucana" }, +] + +[package.optional-dependencies] +codegen = [ + { name = "datamodel-code-generator" }, +] + +[package.dev-dependencies] +dev = [ + { name = "datamodel-code-generator" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + +[package.metadata] +requires-dist = [ + { name = "datamodel-code-generator", marker = "extra == 'codegen'", specifier = ">=0.25" }, + { name = "grpcio", specifier = ">=1.64" }, + { name = "protobuf", specifier = ">=5.0" }, + { name = "pydantic", specifier = ">=2" }, + { name = "tucana", specifier = ">=0.0.80" }, +] +provides-extras = ["codegen"] + +[package.metadata.requires-dev] +dev = [ + { name = "datamodel-code-generator", specifier = ">=0.25" }, + { name = "pytest", specifier = ">=7.4" }, + { name = "pytest-asyncio", specifier = ">=0.23" }, +] + +[[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 = "datamodel-code-generator" +version = "0.72.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "black", marker = "sys_platform != 'emscripten'" }, + { name = "genson" }, + { name = "inflect" }, + { name = "isort", marker = "sys_platform != 'emscripten'" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/b1/a34381cab4c126623d8d59728ffcdb4cb2822877b00484189f1438eae087/datamodel_code_generator-0.72.4.tar.gz", hash = "sha256:b9f0992d46eac4664c4e0b633d93c6e384d39fa8f2df491ac79f05f72ae724fd", size = 1968256, upload-time = "2026-08-12T16:26:06.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/f3/d11738bc4672952b50fa0aa703cc33e17ea432482403c580c6e0d9a7b2d3/datamodel_code_generator-0.72.4-py3-none-any.whl", hash = "sha256:96cb2edbcf3b50c4cc246a48dac5cb5ff848af0675bb4e81235467c47e1038e4", size = 555785, upload-time = "2026-08-12T16:26:04.347Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "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 = "genson" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/53/de162dc8e03fccd9ebe59d17c7812378fe8bd2b604f6b1b94d00165140ac/genson-1.4.0.tar.gz", hash = "sha256:bc7f1c1bae87a21ca44d81149aec95a3f4468d676de9b8b08caa064f3c50b3da", size = 47908, upload-time = "2026-07-06T08:21:50.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/02/767f744ab6d4cb7761e5008acc3d534b7a0481af62563d52e391fbcb2140/genson-1.4.0-py3-none-any.whl", hash = "sha256:03bc71bbe52defde70660cc4dcd1ea1097997da5a1cbb90a9dbd3acc7c9e1b65", size = 24484, upload-time = "2026-07-06T08:21:49.046Z" }, +] + +[[package]] +name = "grpcio" +version = "1.83.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/fd/655c8a773d728bc3c93fb4713ae4bf79ffc75996f86fb78b2974c8e1dfbd/grpcio-1.83.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8", size = 6334247, upload-time = "2026-07-23T15:18:53.099Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9a/1ce5760d35a04a992006dd2f79afff2db548f93ee7426fa95c9f1fc90c61/grpcio-1.83.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727", size = 12168650, upload-time = "2026-07-23T15:18:56.348Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ab/bbcb5be0a1a6cb21f036e2afdd4f7a70147cfb7a7b42648a310d7c43acfc/grpcio-1.83.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5882c1a721b50ce0123ee5e839e1ab059ad72a7ade76cdf2d5bd833b56791acf", size = 6916899, upload-time = "2026-07-23T15:18:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d2/4c27977ecb3b3f9f363b93f570e001cb24ef264a9a907d7fd0f949ed59f0/grpcio-1.83.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e3eedfc92b6b9f2960115e7e620cf0cbf80bb7849a51ce3820dc54dfd88b6b9", size = 7648761, upload-time = "2026-07-23T15:19:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/23/49/0c823a7627ff2e69a61e4a53c4edf215272892fc2c47c6431f033d46f4cc/grpcio-1.83.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fcaa7c45c45b4a89e2867d1f1785d9481a788399d915e341ed2eb49aeef9dd4", size = 7074920, upload-time = "2026-07-23T15:19:02.293Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ce/963f01ff7c789a76909c9691b704112e02ca1e11c10405cd99c2bd7c40f1/grpcio-1.83.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6b6c666a1d5613ff360c9e90f44665e3a88b25a815209ddbc0917eec281931cb", size = 7598046, upload-time = "2026-07-23T15:19:03.921Z" }, + { url = "https://files.pythonhosted.org/packages/eb/de/1ce6bdefc847a7973040d10cebc8996c653a2a687c0a4da8d05dcab4e397/grpcio-1.83.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6be5c807b717be3dd649446f021301fd7907e376318675d2147823071034112a", size = 8634792, upload-time = "2026-07-23T15:19:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8b/7fe6a73895e3bdd788101d1276e48e0d262ebb165afacec1ec4efebcd785/grpcio-1.83.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c834e86d8fd2f03d7e4db49a027f7c5b89c5b88eed305543a5295bd6fee61e40", size = 8000286, upload-time = "2026-07-23T15:19:07.739Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b0/9a779de2bcda8722501a056fad1bec3d1117977af0c080ab1fc0655fdf35/grpcio-1.83.0-cp310-cp310-win32.whl", hash = "sha256:35a5b1c192496b6c25956eebfa963468935612206fd2543ac3ce981e6a5e0f03", size = 4404616, upload-time = "2026-07-23T15:19:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8e/ce9a23590cac33a6c24e6386cc0ffc55821cc13212acc822e98f00a67161/grpcio-1.83.0-cp310-cp310-win_amd64.whl", hash = "sha256:8f6c395e493d20c39b29392ca200e9aaeb78d0bc2f04db0c0a7da7ddc939aa57", size = 5162304, upload-time = "2026-07-23T15:19:11.467Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/a1735f215b2a5cd43c38b79eac072ad197e61be9829905b6b29550abd0db/grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf", size = 7613472, upload-time = "2026-07-23T15:19:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/c9e81f806ac704b6b145cb01628db398985b1f8dfdc10e23b55fb0902b3d/grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd", size = 7040616, upload-time = "2026-07-23T15:19:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ba/94cd5af859876049d340480acbb61a959096c84b567f215534faa78d0424/grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c", size = 7570491, upload-time = "2026-07-23T15:19:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/108d30d5a5c964312ae8b9cb0e8cc5b3c1cc68d8f757cca52b3565534d26/grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5", size = 8605036, upload-time = "2026-07-23T15:19:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/3828ae13c3db8233d123ad612747665817b952d8a954f32390230b582336/grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5", size = 7981587, upload-time = "2026-07-23T15:19:48.913Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/77af31228f55f55a2a5112bb0077ad0a1c4d23dbb0c2853a62475bbdcc14/grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc", size = 4394004, upload-time = "2026-07-23T15:19:50.618Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/f706e39550e7a3732ce2b9c5926107a93d74a802775b19b642a6df27dc96/grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df", size = 5158525, upload-time = "2026-07-23T15:19:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" }, + { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/9c/60/f2cca8147ea213d3e43ae9158d03ad04e020fdf32ff027253e1fe93f921d/grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa", size = 6305607, upload-time = "2026-07-23T15:20:15.353Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ab/d3874931d123a95e83a3ebf8aa04537988fb62425cedb8bf3cefc5ad41b2/grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c", size = 12166617, upload-time = "2026-07-23T15:20:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/92/ff/6f18f9426b69306f4e00a9add3b0ee2748da8aad53836ef80cab0d62d04f/grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b", size = 6880213, upload-time = "2026-07-23T15:20:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/70/21/706d1147c6b93b98f179240c13991fbcc56880eba0c868abb1ad40d8a0a6/grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c", size = 7618335, upload-time = "2026-07-23T15:20:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/1a8443c889115ec9e213a213e86bc93a71ee9088027e5befa09aaa0edd9d/grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df", size = 7043416, upload-time = "2026-07-23T15:20:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/c6/94e0fee5b12bc1da1370185b680988db6f739d19b42d9959db01a7ea50bf/grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b", size = 7583253, upload-time = "2026-07-23T15:20:26.313Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/de1ccb671fb85575bc5192faedf9ecdbdf5b390d2e6584dcf552bcbd370e/grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404", size = 8605102, upload-time = "2026-07-23T15:20:28.437Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/0e0ec749a7034ffcbaa050e39779872950ead90c22e7e0116be3f28b2b46/grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af", size = 7979826, upload-time = "2026-07-23T15:20:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/c3fda157287f64bc65acee6c5aa90c41acf9e0d3a8e69a265eecff6d00a1/grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33", size = 4471765, upload-time = "2026-07-23T15:20:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/b1b26431c9d54eee11724fd6e5585473a2ed47fbc1fb95e5204906a642ce/grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9", size = 5298932, upload-time = "2026-07-23T15:20:35.48Z" }, +] + +[[package]] +name = "inflect" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +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 = "isort" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +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 = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +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" }, +] + +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +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 = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/98/0bf930c4f97d0266b58a89e36c015f56232c52b5d2f207215d48cca9e8f7/platformdirs-4.11.2.tar.gz", hash = "sha256:3a2ae5fca3520a01ab1be8b45613537f52ddf5b5f6f53d88233892dfbf0cd82d", size = 32716, upload-time = "2026-08-10T15:48:06.092Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e2/4e6eee633809c376c024821b91ade709cbfd040ec53939ffbcc292aa7eee/platformdirs-4.11.2-py3-none-any.whl", hash = "sha256:7f89089b6ea71bda7962953edcf784b2e2d9d285b40ad88be2bb75c6e9d82ab4", size = 23361, upload-time = "2026-08-10T15:48:04.855Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +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 = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +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/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" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tucana" +version = "0.0.80" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/56/a409c44d53bf4f63a77d6c0d9e5dadaac3a59cfa02e89f3b00cfb85849a5/tucana-0.0.80.tar.gz", hash = "sha256:b249fbb505306d1ffd77fbdc6ac769bae25a82a9c9e2776d5062ea1b7bce1b2f", size = 21834, upload-time = "2026-08-04T14:09:37.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/8f/cdcf8e5d6c520ceca0816a83df7dfb9b8d3dcf5cb697c269f88c21b9e373/tucana-0.0.80-py3-none-any.whl", hash = "sha256:6c6331d31cb59455e7c0839720273eb6347daed093f502dbd73e0b3b81e40fda", size = 66111, upload-time = "2026-08-04T14:09:37.98Z" }, +] + +[[package]] +name = "typeguard" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/de/4420db493fa8fc0856d5e5c1b159c63a323d2de2317babe36b01568928e8/typeguard-4.6.0.tar.gz", hash = "sha256:e7414f09111317de3e335de92cd397c5c0ca00b1cc1676de12e1d444a79b3f21", size = 82330, upload-time = "2026-07-26T08:40:23.207Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/eb/461d5f167b6f5c7d97696f397c82f82e3480e003fce3f0a1cd1dd26e2eb2/typeguard-4.6.0-py3-none-any.whl", hash = "sha256:79878165bb86f2cf5d41d159a0ff1792a796cf496882d2fe1b1c6c7049b9cdd7", size = 36884, upload-time = "2026-07-26T08:40:21.868Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] From 659896917c172a04683234f25ab3a79160bee467 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 14:56:50 +0200 Subject: [PATCH 03/12] feat: add build_definitions.py for generating Python modules from code0-definition releases --- py/scripts/build_definitions.py | 359 ++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 py/scripts/build_definitions.py diff --git a/py/scripts/build_definitions.py b/py/scripts/build_definitions.py new file mode 100644 index 0000000..3636c4f --- /dev/null +++ b/py/scripts/build_definitions.py @@ -0,0 +1,359 @@ +"""Generate the ``hercules/definitions`` package from a code0-definition release. + +Python port of ``ts/scripts/build-definitions.ts``. Downloads the upstream +``definitions.zip``, then for every definition emits a Python module: + +* data types -> a schema variable + ``register_schema`` call +* runtime funcs -> a decorated ``RuntimeFunctionRunnable`` subclass +* runtime flows -> a decorated ``RuntimeEventRunnable`` subclass + +Usage:: + + python scripts/build_definitions.py --version def-0.0.34 +""" +from __future__ import annotations + +import argparse +import io +import json +import os +import re +import shutil +import sys +import urllib.request +import zipfile +from pathlib import Path +from typing import Dict, List, Optional, Set, Tuple + +# ``build_definitions`` runs standalone; make sure the package is importable. +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +from hercules.internal.pydantic_codegen import ts_to_pydantic + +REPO = "code0-tech/code0-definition" +OUTPUT_DIR = Path(__file__).resolve().parent.parent / "hercules" / "definitions" + + +# ── Naming helpers ───────────────────────────────────────────────────────────── + +def to_pascal_case(value: str) -> str: + value = value.replace("::", "_") + parts = [p for p in _split_words(value) if p] + return "".join(p[:1].upper() + p[1:].lower() for p in parts) + + +def _split_words(value: str) -> List[str]: + out, cur = [], "" + for ch in value: + if ch in "_-": + out.append(cur) + cur = "" + else: + cur += ch + out.append(cur) + return out + + +def sanitize_segment(segment: str) -> str: + """Make a directory name a valid Python package name (``rest-action`` -> ``rest_action``).""" + return segment.replace("-", "_") + + +def to_model_name(identifier: str) -> str: + """Class name for a data type's generated Pydantic model (``OBJECT`` -> ``Object``).""" + name = to_pascal_case(identifier) + if not name or not name[0].isalpha(): + name = "DataType" + name + return name + + +_TS_IDENT_RE = re.compile(r"^[A-Za-z_$][A-Za-z0-9_$]*$") + + +def module_dotted(rel_module: str, file_name: str) -> str: + segments = [sanitize_segment(s) for s in Path(rel_module).parts] + return ".".join(["hercules", "definitions", *segments, file_name]) + + +# ── Definition records ───────────────────────────────────────────────────────── + +class DataTypeDef: + def __init__(self, identifier, type_str, generic_keys, rel_module, file_name): + self.identifier = identifier + self.type_str = type_str + self.generic_keys = generic_keys + self.rel_module = rel_module + self.file_name = file_name + + +# ── Translation / decorator rendering ────────────────────────────────────────── + +def _py_translations(items) -> str: + return "[" + ", ".join( + f'{{"code": "{t["code"]}", "content": {json.dumps(t["content"])}}}' for t in items + ) + "]" + + +def _translation_decorator(name: str, items) -> List[str]: + if not items: + return [] + args = ", ".join( + f'{{"code": "{t["code"]}", "content": {json.dumps(t["content"])}}}' for t in items + ) + return [f"@{name}({args})"] + + +def _meta_decorators(json_obj: dict, identifier_key: str) -> Tuple[List[str], List[str]]: + """Return (decorator lines, imported decorator names).""" + lines: List[str] = [f"@Identifier({json.dumps(json_obj[identifier_key])})"] + names = {"Identifier"} + if json_obj.get("signature"): + lines.append(f"@Signature({json.dumps(json_obj['signature'])})") + names.add("Signature") + for key, deco in ( + ("name", "Name"), + ("description", "Description"), + ("documentation", "Documentation"), + ("displayMessage", "DisplayMessage"), + ("alias", "Alias"), + ): + if json_obj.get(key): + lines += _translation_decorator(deco, json_obj[key]) + names.add(deco) + if json_obj.get("displayIcon"): + lines.append(f"@DisplayIcon({json.dumps(json_obj['displayIcon'])})") + names.add("DisplayIcon") + return lines, sorted(names) + + +def _parameter_decorators(params) -> List[str]: + out = [] + for p in params or []: + props = [f'"runtime_name": {json.dumps(p["runtimeName"])}'] + if p.get("name"): + props.append(f'"name": {_py_translations(p["name"])}') + if p.get("description"): + props.append(f'"description": {_py_translations(p["description"])}') + if p.get("optional"): + props.append('"optional": True') + if p.get("hidden"): + props.append('"hidden": True') + out.append("@Parameter({" + ", ".join(props) + "})") + return out + + +def _setting_decorators(settings) -> List[str]: + out = [] + for s in settings or []: + props = [f'"identifier": {json.dumps(s["identifier"])}'] + if s.get("unique") and s["unique"] != "NONE": + props.append(f'"unique": {json.dumps(s["unique"])}') + if s.get("name"): + props.append(f'"name": {_py_translations(s["name"])}') + if s.get("description"): + props.append(f'"description": {_py_translations(s["description"])}') + if s.get("optional"): + props.append('"optional": True') + if s.get("hidden"): + props.append('"hidden": True') + out.append("@EventSetting({" + ", ".join(props) + "})") + return out + + +# ── File generators ──────────────────────────────────────────────────────────── + +def generate_runtime_function(json_obj: dict, class_name: str) -> str: + params = json_obj.get("runtimeParameterDefinitions") + meta_lines, meta_names = _meta_decorators(json_obj, "runtimeName") + func_names = [] + if json_obj.get("throwsError"): + func_names.append("ThrowsError") + if params: + func_names.append("Parameter") + + lines = [ + "from hercules.models.runtime_function import RuntimeFunctionRunnable", + f"from hercules.decorators.meta import {', '.join(meta_names)}", + "from hercules.decorators.runtime_function import OmitRuntimeFunction", + ] + if func_names: + lines.append(f"from hercules.decorators.function import {', '.join(func_names)}") + lines += ["", "", "@OmitRuntimeFunction()"] + lines += meta_lines + if json_obj.get("throwsError"): + lines.append("@ThrowsError()") + lines += _parameter_decorators(params) + lines += [ + f"class {class_name}(RuntimeFunctionRunnable):", + " def run(self, *args):", + f' raise NotImplementedError("{class_name}.run() is not implemented")', + "", + ] + return "\n".join(lines) + + +def generate_runtime_flow_type(json_obj: dict, class_name: str) -> str: + settings = json_obj.get("runtimeSettings") + meta_lines, meta_names = _meta_decorators(json_obj, "identifier") + + lines = [ + "from hercules.models.runtime_event import RuntimeEventRunnable", + f"from hercules.decorators.meta import {', '.join(meta_names)}", + ] + if settings: + lines.append("from hercules.decorators.event import EventSetting") + lines += ["", ""] + lines += meta_lines + lines += _setting_decorators(settings) + lines += [f"class {class_name}(RuntimeEventRunnable):", " pass", ""] + return "\n".join(lines) + + +def _strip_codegen_header(code: str) -> str: + """Drop the leading ``# generated by datamodel-codegen`` banner.""" + lines = code.splitlines() + start = 0 + while start < len(lines) and ( + lines[start].startswith("#") or not lines[start].strip() + ): + start += 1 + return "\n".join(lines[start:]).strip() + + +def _reference_preamble(dt: "DataTypeDef", ref_map, generic_key_set: Set[str]) -> str: + """Declare every other data-type identifier / generic key as an opaque TS type. + + The referenced data types are only *registered* for reference resolution, so it + is enough for the generator to see them as opaque (``unknown``) here; the exact + cross-references are preserved separately in the emitted ``TypeString``. + """ + names = set(ref_map) | set(generic_key_set) | set(dt.generic_keys) + names.discard(dt.identifier) + stubs = [ + f"type {name} = unknown;" + for name in sorted(names) + if _TS_IDENT_RE.match(name) + ] + return "\n".join(stubs) + + +def generate_data_type(dt: "DataTypeDef", ref_map, generic_key_set) -> str: + class_name = to_model_name(dt.identifier) + preamble = _reference_preamble(dt, ref_map, generic_key_set) + + try: + model_code = _strip_codegen_header( + ts_to_pydantic(dt.type_str, class_name, preamble=preamble) + ) + except Exception as exc: # noqa: BLE001 - keep the build resilient per-definition + print(f" ! {dt.identifier}: falling back to RootModel[Any] ({exc})") + model_code = ( + "from typing import Any\n" + "from pydantic import RootModel\n\n\n" + f"class {class_name}(RootModel[Any]):\n pass" + ) + + lines = [ + model_code, + "", + "", + "from hercules.internal.schema import register_schema", + f"register_schema({class_name}, {json.dumps(dt.identifier)})", + "", + ] + return "\n".join(lines) + + +# ── Directory walking ────────────────────────────────────────────────────────── + +def walk_defs(defs_dir: Path): + for path in sorted(defs_dir.rglob("*.json")): + if path.name == "module.json": + continue + rel_module = str(path.parent.relative_to(defs_dir)) + type_folder = path.parent.name + class_name = to_pascal_case(path.stem) + file_name = path.stem.lower() + data = json.loads(path.read_text()) + yield data, class_name, file_name, rel_module, type_folder + + +def ensure_package(path: Path): + path.mkdir(parents=True, exist_ok=True) + init = path / "__init__.py" + if not init.exists(): + init.write_text("") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--version", required=True) + args = parser.parse_args() + version = args.version + + url = f"https://github.com/{REPO}/releases/download/{version}/definitions.zip" + print(f"Downloading {url}...") + with urllib.request.urlopen(url) as response: + payload = response.read() + + extract_dir = Path(os.environ.get("TMPDIR", "/tmp")) / f"hercules-defs-{version}" + if extract_dir.exists(): + shutil.rmtree(extract_dir) + with zipfile.ZipFile(io.BytesIO(payload)) as zf: + zf.extractall(extract_dir) + defs_dir = extract_dir / "definitions" + + # Collect data type defs first (needed to resolve references). + data_type_defs: List[DataTypeDef] = [] + generic_key_set: Set[str] = set() + for data, class_name, file_name, rel_module, type_folder in walk_defs(defs_dir): + if type_folder != "data_types" or not data.get("identifier") or not data.get("type"): + continue + keys = [k.split("extends")[0].strip() for k in (data.get("genericKeys") or [])] + generic_key_set.update(keys) + data_type_defs.append( + DataTypeDef(data["identifier"], data["type"], set(keys), rel_module, file_name) + ) + ref_map = {d.identifier: d for d in data_type_defs} + + if OUTPUT_DIR.exists(): + shutil.rmtree(OUTPUT_DIR) + ensure_package(OUTPUT_DIR) + + generated: List[str] = [] + + def write_file(rel_module: str, file_name: str, content: str): + segments = [sanitize_segment(s) for s in Path(rel_module).parts] + out_dir = OUTPUT_DIR + for seg in segments: + out_dir = out_dir / seg + ensure_package(out_dir) + (out_dir / f"{file_name}.py").write_text(content) + generated.append(".".join([*segments, file_name])) + + # Data types. + for dt in data_type_defs: + write_file(dt.rel_module, dt.file_name, generate_data_type(dt, ref_map, dt.generic_keys)) + + # Runtime functions and flow types. + for data, class_name, file_name, rel_module, type_folder in walk_defs(defs_dir): + if type_folder == "runtime_flow_types": + content = generate_runtime_flow_type(data, class_name) + elif type_folder == "runtime_functions": + content = generate_runtime_function(data, class_name) + else: + continue + write_file(rel_module, file_name, content) + + # Top-level package imports every generated module (runs registration). + index_lines = [ + f"import hercules.definitions.{name} # noqa: F401" + for name in sorted(generated) + ] + (OUTPUT_DIR / "__init__.py").write_text("\n".join(index_lines) + "\n") + + shutil.rmtree(extract_dir, ignore_errors=True) + print(f"Generated {len(generated)} definition files in {OUTPUT_DIR}") + + +if __name__ == "__main__": + sys.exit(main()) From 4a7f4e41d3b38795aee9a43f43cee50439adc6f6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 14:59:54 +0200 Subject: [PATCH 04/12] feat: add initial implementation of event and function managers with mapping support --- py/hercules/__init__.py | 81 ++++ py/hercules/_metadata.py | 38 ++ py/hercules/_tsgen/convert.mjs | 96 +++++ py/hercules/_tsgen/package-lock.json | 366 +++++++++++++++++++ py/hercules/_tsgen/package.json | 11 + py/hercules/_tucana/__init__.py | 29 ++ py/hercules/_tucana/helpers.py | 73 ++++ py/hercules/action.py | 334 +++++++++++++++++ py/hercules/actions/__init__.py | 21 ++ py/hercules/actions/execution.py | 134 +++++++ py/hercules/actions/flow_execution.py | 11 + py/hercules/actions/flow_update.py | 18 + py/hercules/actions/module_configurations.py | 14 + py/hercules/actions/sub_flow_execution.py | 14 + py/hercules/cli.py | 137 +++++++ py/hercules/decorators/__init__.py | 0 py/hercules/decorators/datatype.py | 39 ++ py/hercules/decorators/event.py | 35 ++ py/hercules/decorators/function.py | 59 +++ py/hercules/decorators/meta.py | 82 +++++ py/hercules/decorators/runtime_event.py | 15 + py/hercules/decorators/runtime_function.py | 18 + py/hercules/events.py | 23 ++ py/hercules/internal/__init__.py | 0 py/hercules/internal/connection.py | 37 ++ py/hercules/internal/module_builder.py | 261 +++++++++++++ py/hercules/internal/pydantic_codegen.py | 82 +++++ py/hercules/internal/schema.py | 158 ++++++++ py/hercules/internal/tsgen.py | 93 +++++ py/hercules/manager/__init__.py | 0 py/hercules/manager/base.py | 73 ++++ py/hercules/manager/config.py | 45 +++ py/hercules/manager/datatype.py | 8 + py/hercules/manager/event.py | 8 + py/hercules/manager/flow.py | 12 + py/hercules/manager/function.py | 8 + py/hercules/manager/runtime_event.py | 8 + py/hercules/manager/runtime_function.py | 8 + py/hercules/map/__init__.py | 0 py/hercules/map/datatype.py | 41 +++ py/hercules/map/event.py | 74 ++++ py/hercules/map/function.py | 76 ++++ py/hercules/map/runtime_event.py | 43 +++ py/hercules/map/runtime_function.py | 64 ++++ py/hercules/models/__init__.py | 0 py/hercules/models/datatype.py | 49 +++ py/hercules/models/event.py | 35 ++ py/hercules/models/function.py | 37 ++ py/hercules/models/runtime_event.py | 29 ++ py/hercules/models/runtime_function.py | 33 ++ py/hercules/schema.py | 38 ++ py/hercules/types.py | 75 ++++ 52 files changed, 3043 insertions(+) create mode 100644 py/hercules/__init__.py create mode 100644 py/hercules/_metadata.py create mode 100644 py/hercules/_tsgen/convert.mjs create mode 100644 py/hercules/_tsgen/package-lock.json create mode 100644 py/hercules/_tsgen/package.json create mode 100644 py/hercules/_tucana/__init__.py create mode 100644 py/hercules/_tucana/helpers.py create mode 100644 py/hercules/action.py create mode 100644 py/hercules/actions/__init__.py create mode 100644 py/hercules/actions/execution.py create mode 100644 py/hercules/actions/flow_execution.py create mode 100644 py/hercules/actions/flow_update.py create mode 100644 py/hercules/actions/module_configurations.py create mode 100644 py/hercules/actions/sub_flow_execution.py create mode 100644 py/hercules/cli.py create mode 100644 py/hercules/decorators/__init__.py create mode 100644 py/hercules/decorators/datatype.py create mode 100644 py/hercules/decorators/event.py create mode 100644 py/hercules/decorators/function.py create mode 100644 py/hercules/decorators/meta.py create mode 100644 py/hercules/decorators/runtime_event.py create mode 100644 py/hercules/decorators/runtime_function.py create mode 100644 py/hercules/events.py create mode 100644 py/hercules/internal/__init__.py create mode 100644 py/hercules/internal/connection.py create mode 100644 py/hercules/internal/module_builder.py create mode 100644 py/hercules/internal/pydantic_codegen.py create mode 100644 py/hercules/internal/schema.py create mode 100644 py/hercules/internal/tsgen.py create mode 100644 py/hercules/manager/__init__.py create mode 100644 py/hercules/manager/base.py create mode 100644 py/hercules/manager/config.py create mode 100644 py/hercules/manager/datatype.py create mode 100644 py/hercules/manager/event.py create mode 100644 py/hercules/manager/flow.py create mode 100644 py/hercules/manager/function.py create mode 100644 py/hercules/manager/runtime_event.py create mode 100644 py/hercules/manager/runtime_function.py create mode 100644 py/hercules/map/__init__.py create mode 100644 py/hercules/map/datatype.py create mode 100644 py/hercules/map/event.py create mode 100644 py/hercules/map/function.py create mode 100644 py/hercules/map/runtime_event.py create mode 100644 py/hercules/map/runtime_function.py create mode 100644 py/hercules/models/__init__.py create mode 100644 py/hercules/models/datatype.py create mode 100644 py/hercules/models/event.py create mode 100644 py/hercules/models/function.py create mode 100644 py/hercules/models/runtime_event.py create mode 100644 py/hercules/models/runtime_function.py create mode 100644 py/hercules/schema.py create mode 100644 py/hercules/types.py diff --git a/py/hercules/__init__.py b/py/hercules/__init__.py new file mode 100644 index 0000000..b99d61b --- /dev/null +++ b/py/hercules/__init__.py @@ -0,0 +1,81 @@ +"""hercules — the action SDK to connect with aquila (Python). + +Public API mirror of the TypeScript ``src/index.ts`` re-exports. +""" +from __future__ import annotations + +# Must run before any module that imports the tucana protobufs: it puts +# ``tucana/generated`` on sys.path so the generated protos' internal +# ``from shared import ...`` / ``from aquila import ...`` imports resolve. +import hercules._tucana # noqa: F401 isort: skip + +from hercules.types import ( + ConfigurationDefinition, + FlowTypeSetting_UniquenessScope, + FunctionContext, + PlainValue, + ProjectConfiguration, + RuntimeError, + RuntimeFlowTypeSetting_UniquenessScope, + SubFlow, + Translation, +) +from hercules.events import CodeZeroEvent +from hercules.schema import ( + Annotated, + BaseModel, + ConfigDict, + Field, + RootModel, + StringConstraints, + Schema as SchemaType, +) +from hercules.decorators.meta import ( + Alias, + DeprecationMessage, + Description, + DisplayIcon, + DisplayMessage, + Documentation, + Editable, + Identifier, + Name, + Signature, +) +from hercules.decorators.function import Design, OmitFunction, Parameter, ThrowsError +from hercules.decorators.datatype import Schema, TypeString +from hercules.decorators.event import EventSetting +from hercules.decorators.runtime_event import OmitEvent +from hercules.decorators.runtime_function import OmitRuntimeFunction +from hercules.manager.base import BaseManager +from hercules.manager.config import ConfigManager +from hercules.manager.datatype import DataTypeManager +from hercules.manager.event import EventManager +from hercules.manager.flow import FlowManager +from hercules.manager.function import FunctionManager +from hercules.manager.runtime_event import RuntimeEventManager +from hercules.manager.runtime_function import RuntimeFunctionManager +from hercules.models.datatype import DataTypeProps, DataTypeRunnable +from hercules.models.event import EventModel, EventSettingProps +from hercules.models.function import FunctionParameterProps, FunctionProps +from hercules.models.runtime_event import RuntimeEventProps, RuntimeEventRunnable +from hercules.models.runtime_function import RuntimeFunctionProps, RuntimeFunctionRunnable +from hercules.action import Action, is_export_mode, registered_actions + +__all__ = [ + "Translation", "PlainValue", "SubFlow", "FunctionContext", + "ProjectConfiguration", "ConfigurationDefinition", "RuntimeError", + "FlowTypeSetting_UniquenessScope", "RuntimeFlowTypeSetting_UniquenessScope", + "CodeZeroEvent", "SchemaType", + "BaseModel", "RootModel", "Field", "StringConstraints", "ConfigDict", "Annotated", + "Identifier", "Name", "Description", "Documentation", "DisplayMessage", + "Alias", "DeprecationMessage", "Signature", "DisplayIcon", "Editable", + "OmitFunction", "Design", "ThrowsError", "Parameter", "Schema", "TypeString", + "EventSetting", "OmitEvent", "OmitRuntimeFunction", + "BaseManager", "ConfigManager", "FlowManager", "FunctionManager", + "RuntimeFunctionManager", "DataTypeManager", "EventManager", "RuntimeEventManager", + "FunctionProps", "FunctionParameterProps", "RuntimeFunctionProps", + "RuntimeFunctionRunnable", "DataTypeProps", "DataTypeRunnable", "EventModel", + "EventSettingProps", "RuntimeEventProps", "RuntimeEventRunnable", + "Action", "is_export_mode", "registered_actions", +] diff --git a/py/hercules/_metadata.py b/py/hercules/_metadata.py new file mode 100644 index 0000000..cb44e58 --- /dev/null +++ b/py/hercules/_metadata.py @@ -0,0 +1,38 @@ +"""Class metadata store — Python equivalent of ``reflect-metadata``. + +Metadata is attached to classes under a private per-class dict. ``get_metadata`` +walks the MRO (like ``Reflect.getMetadata`` walking the prototype chain), while +``get_own_metadata`` only inspects the class itself (like ``getOwnMetadata``). +""" +from __future__ import annotations + +from typing import Any + +_OWN = "__hercules_own_meta__" + + +def _own_dict(target: type) -> dict: + # Only create/return the dict that belongs to ``target`` itself, never an + # inherited one — mirrors ``Reflect.getOwnMetadata`` semantics. + if _OWN not in target.__dict__: + setattr(target, _OWN, {}) + return target.__dict__[_OWN] + + +def define_metadata(key: str, value: Any, target: type) -> None: + _own_dict(target)[key] = value + + +def get_own_metadata(key: str, target: type, default: Any = None) -> Any: + own = target.__dict__.get(_OWN) + if own is None: + return default + return own.get(key, default) + + +def get_metadata(key: str, target: type, default: Any = None) -> Any: + for klass in getattr(target, "__mro__", (target,)): + own = klass.__dict__.get(_OWN) + if own is not None and key in own: + return own[key] + return default diff --git a/py/hercules/_tsgen/convert.mjs b/py/hercules/_tsgen/convert.mjs new file mode 100644 index 0000000..a8f2952 --- /dev/null +++ b/py/hercules/_tsgen/convert.mjs @@ -0,0 +1,96 @@ +/** + * Schema conversion helper for the hercules Python SDK. + * + * Reads a single JSON request from stdin and writes a JSON response to stdout. + * Two modes are supported: + * + * {"mode": "json_schema_to_ts", "schema": , "name": ""} + * -> {"type": ""} + * + * {"mode": "ts_to_json_schema", "ts": "", "name": "", + * "preamble": ""} + * -> {"schema": } + * + * The Python side (hercules.internal.schema / hercules.internal.pydantic_codegen) + * is responsible for pre-processing JSON schemas (resolving refs, injecting + * `tsType` identifier references, stripping titles) before calling this helper. + */ +import {compile} from "json-schema-to-typescript"; +import {createGenerator} from "ts-json-schema-generator"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; + +function readStdin() { + return new Promise((resolve, reject) => { + let data = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", chunk => (data += chunk)); + process.stdin.on("end", () => resolve(data)); + process.stdin.on("error", reject); + }); +} + +/** Collapse json-schema-to-typescript output into a single inline type expression. */ +function inlineType(tsText, name) { + const blocks = tsText.split(/\n(?=export )/).map(b => b.trim()).filter(Boolean); + const collapse = s => s.replace(/\s+/g, " ").trim(); + let target = blocks[0]; + if (blocks.length > 1) { + const byName = blocks.find(b => + new RegExp(`^export (?:interface|type)\\s+${name}\\b`).test(b)); + if (byName) target = byName; + } + const iface = target.match(/^export interface \S+\s*({[\s\S]*})\s*$/); + if (iface) return collapse(iface[1]); + const alias = target.match(/^export type \S+\s*=\s*([\s\S]*?);?\s*$/); + if (alias) return collapse(alias[1]); + return collapse(target); +} + +async function jsonSchemaToTs(req) { + const ts = await compile(req.schema, req.name, { + bannerComment: "", + format: true, + additionalProperties: false, + unknownAny: true, + }); + return {type: inlineType(ts, req.name)}; +} + +function tsToJsonSchema(req) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hercules-tsgen-")); + const file = path.join(dir, "input.ts"); + const preamble = req.preamble ? req.preamble + "\n" : ""; + fs.writeFileSync(file, `${preamble}export type ${req.name} = ${req.ts};\n`); + try { + const generator = createGenerator({ + path: file, + type: req.name, + skipTypeCheck: true, + additionalProperties: false, + expose: "none", + }); + return {schema: generator.createSchema(req.name)}; + } finally { + fs.rmSync(dir, {recursive: true, force: true}); + } +} + +async function main() { + const req = JSON.parse(await readStdin()); + let res; + if (req.mode === "json_schema_to_ts") { + res = await jsonSchemaToTs(req); + } else if (req.mode === "ts_to_json_schema") { + res = tsToJsonSchema(req); + } else { + throw new Error(`Unknown mode: ${req.mode}`); + } + process.stdout.write(JSON.stringify(res)); +} + +main().catch(err => { + process.stderr.write(String(err && err.stack ? err.stack : err)); + process.exit(1); +}); diff --git a/py/hercules/_tsgen/package-lock.json b/py/hercules/_tsgen/package-lock.json new file mode 100644 index 0000000..e36d70b --- /dev/null +++ b/py/hercules/_tsgen/package-lock.json @@ -0,0 +1,366 @@ +{ + "name": "hercules-tsgen", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hercules-tsgen", + "version": "0.0.0", + "dependencies": { + "json-schema-to-typescript": "^15.0.0", + "ts-json-schema-generator": "^2.3.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-json-schema-generator": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-2.9.0.tgz", + "integrity": "sha512-NR5ZE108uiPtBHBJNGnhwoUaUx5vWTDJzDFG9YlRoqxPU76n+5FClRh92dcGgysbe1smRmYalM9Saj97GW1J4Q==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "commander": "^14.0.3", + "glob": "^13.0.6", + "json5": "^2.2.3", + "normalize-path": "^3.0.0", + "safe-stable-stringify": "^2.5.0", + "tslib": "^2.8.1", + "typescript": "^5.9.3" + }, + "bin": { + "ts-json-schema-generator": "bin/ts-json-schema-generator.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/py/hercules/_tsgen/package.json b/py/hercules/_tsgen/package.json new file mode 100644 index 0000000..4893e94 --- /dev/null +++ b/py/hercules/_tsgen/package.json @@ -0,0 +1,11 @@ +{ + "name": "hercules-tsgen", + "version": "0.0.0", + "private": true, + "description": "Node helper for hercules: Pydantic JSON-schema <-> TypeScript type-string conversion.", + "type": "module", + "dependencies": { + "json-schema-to-typescript": "^15.0.0", + "ts-json-schema-generator": "^2.3.0" + } +} diff --git a/py/hercules/_tucana/__init__.py b/py/hercules/_tucana/__init__.py new file mode 100644 index 0000000..0955832 --- /dev/null +++ b/py/hercules/_tucana/__init__.py @@ -0,0 +1,29 @@ +"""Runtime bootstrap for the ``tucana`` protobuf package. + +The generated protobuf modules published in ``tucana`` cross-import each other +with top-level roots (``from shared import ...``, ``from aquila import ...``), so +the ``tucana/generated`` directory has to be on ``sys.path`` for them to import. + +Importing this package performs that wiring once. The rest of the SDK then imports +the proto modules from their real location (``tucana.generated.shared`` / +``tucana.generated.aquila``) so that static analysis and IDEs can resolve them. + +The struct <-> value helpers live in :mod:`hercules._tucana.helpers`. +""" +from __future__ import annotations + +import sys + +import tucana.generated as _generated + + +def ensure_tucana_path() -> None: + """Put ``tucana/generated`` on ``sys.path`` (idempotent).""" + root = list(_generated.__path__)[0] + if root not in sys.path: + sys.path.insert(0, root) + + +ensure_tucana_path() + +__all__ = ["ensure_tucana_path"] diff --git a/py/hercules/_tucana/helpers.py b/py/hercules/_tucana/helpers.py new file mode 100644 index 0000000..b3e93c0 --- /dev/null +++ b/py/hercules/_tucana/helpers.py @@ -0,0 +1,73 @@ +"""Struct <-> plain value helpers. + +Python port of ``@code0-tech/tucana/helpers`` (``shared.struct_helper``). +Converts between the protobuf ``shared.Value`` message and plain Python values. +""" +from __future__ import annotations + +from typing import Union + +from tucana.generated.shared import struct_pb2 + +# Mirror of the TS ``PlainValue`` union. +PlainValue = Union[None, bool, int, float, str, list, dict] + +__all__ = ["PlainValue", "construct_value", "to_allowed_value"] + + +def to_allowed_value(value: struct_pb2.Value) -> PlainValue: + """Convert a protobuf ``Value`` into a plain Python value.""" + kind = value.WhichOneof("kind") + if kind is None or kind == "null_value": + return None + if kind == "number_value": + number = value.number_value + which = number.WhichOneof("number") + if which == "integer": + return int(number.integer) + if which == "float": + return number.float + raise ValueError(f"Unsupported NumberValue number kind: {which}") + if kind == "string_value": + return value.string_value + if kind == "bool_value": + return value.bool_value + if kind == "list_value": + return [to_allowed_value(v) for v in value.list_value.values] + if kind == "struct_value": + return {k: to_allowed_value(v) for k, v in value.struct_value.fields.items()} + raise ValueError(f"Unsupported Value kind: {kind}") + + +def construct_value(value: PlainValue) -> struct_pb2.Value: + """Convert a plain Python value into a protobuf ``Value``.""" + if value is None: + return struct_pb2.Value(null_value=struct_pb2.NULL_VALUE) + # bool must be checked before int (bool is a subclass of int in Python). + if isinstance(value, bool): + return struct_pb2.Value(bool_value=value) + if isinstance(value, int): + return struct_pb2.Value( + number_value=struct_pb2.NumberValue(integer=value) + ) + if isinstance(value, float): + if value.is_integer(): + return struct_pb2.Value( + number_value=struct_pb2.NumberValue(integer=int(value)) + ) + return struct_pb2.Value(number_value=struct_pb2.NumberValue(float=value)) + if isinstance(value, str): + return struct_pb2.Value(string_value=value) + if isinstance(value, (list, tuple)): + return struct_pb2.Value( + list_value=struct_pb2.ListValue( + values=[construct_value(v) for v in value] + ) + ) + if isinstance(value, dict): + return struct_pb2.Value( + struct_value=struct_pb2.Struct( + fields={k: construct_value(v) for k, v in value.items()} + ) + ) + raise ValueError(f"Unsupported value type: {type(value).__name__}") diff --git a/py/hercules/action.py b/py/hercules/action.py new file mode 100644 index 0000000..e63ff1d --- /dev/null +++ b/py/hercules/action.py @@ -0,0 +1,334 @@ +"""The :class:`Action` — the SDK entry point (port of ``src/action.ts``).""" +from __future__ import annotations + +import asyncio +import inspect +import os +import uuid +from dataclasses import replace +from typing import Callable, Dict, List, Optional + +from hercules._metadata import get_metadata +from tucana.generated.aquila import action_pb2 +from hercules._tucana.helpers import PlainValue, construct_value, to_allowed_value +from hercules.actions import actions as _packet_handlers +from hercules.events import CodeZeroEvent +from hercules.internal.connection import create_connection +from hercules.internal.module_builder import ModuleBuildData, build_module +from hercules.manager.config import ConfigManager +from hercules.manager.datatype import DataTypeManager +from hercules.manager.event import EventManager +from hercules.manager.flow import FlowManager +from hercules.manager.function import FunctionManager +from hercules.manager.runtime_event import RuntimeEventManager +from hercules.manager.runtime_function import RuntimeFunctionManager +from hercules.map.datatype import data_type_map +from hercules.map.event import event_map +from hercules.map.function import function_map +from hercules.map.runtime_event import runtime_event_map +from hercules.map.runtime_function import runtime_function_map +from hercules.models.event import EventModel +from hercules.models.function import FunctionProps +from hercules.types import ConfigurationDefinition, RuntimeError, Translation + +# Registry of actions constructed while exporting, so the CLI can find them. +_REGISTERED_ACTIONS: List["Action"] = [] + + +def is_export_mode() -> bool: + return os.environ.get("HERCULES_EXPORT") == "1" + + +def registered_actions() -> List["Action"]: + return list(_REGISTERED_ACTIONS) + + +class EventEmitter: + """A minimal Node-style event emitter.""" + + def __init__(self) -> None: + self._listeners: Dict[str, List[Callable]] = {} + + def on(self, event, listener: Callable) -> "EventEmitter": + self._listeners.setdefault(str(event), []).append(listener) + return self + + def once(self, event, listener: Callable) -> "EventEmitter": + def wrapper(*args): + self.off(event, wrapper) + return listener(*args) + + return self.on(event, wrapper) + + def off(self, event, listener: Callable) -> "EventEmitter": + listeners = self._listeners.get(str(event)) + if listeners and listener in listeners: + listeners.remove(listener) + return self + + def emit(self, event, *args) -> bool: + listeners = list(self._listeners.get(str(event), [])) + for listener in listeners: + result = listener(*args) + if inspect.isawaitable(result): + asyncio.ensure_future(result) + return bool(listeners) + + +class Action(EventEmitter): + def __init__( + self, + identifier: str, + version: str, + aquila_url: Optional[str], + author: str, + icon: str, + documentation: str, + name: List[Translation], + configuration_definitions: Optional[List[ConfigurationDefinition]] = None, + ) -> None: + super().__init__() + self._identifier = identifier + self._version = version + self._aquila_url = aquila_url + self._author = author + self._icon = icon + self._documentation = documentation + self._name = name + self._configuration_definitions = [ + c if isinstance(c, ConfigurationDefinition) else ConfigurationDefinition(**c) + for c in (configuration_definitions or []) + ] + + self._channel = None + self._stream = None + self._actions = {a.packet_type: a.handle for a in _packet_handlers} + # Pending sub flow / flow execution requests awaiting a response, keyed + # by execution identifier. A queue matches responses FIFO because a sub + # flow can be executed repeatedly under the same execution identifier. + self._pending_executions: Dict[str, List[asyncio.Future]] = {} + + self.configs = ConfigManager() + self.flows = FlowManager() + self.functions = FunctionManager() + self.runtime_functions = RuntimeFunctionManager() + self.data_types = DataTypeManager() + self.events = EventManager() + self.runtime_events = RuntimeEventManager() + + if is_export_mode(): + _REGISTERED_ACTIONS.append(self) + + @property + def identifier(self) -> str: + return self._identifier + + @property + def version(self) -> str: + return self._version + + @property + def stream(self): + return self._stream + + # --- registration --------------------------------------------------------- + def register_function(self, klass: type) -> None: + definition = function_map(klass) + self.functions.set(definition.runtime_name, definition) + + def register_runtime_function(self, klass: type) -> None: + omit_definition = get_metadata("hercules:omit_function_definition", klass) or False + definition = runtime_function_map(klass) + self.runtime_functions.set(definition.runtime_name, definition) + if not omit_definition: + self.functions.set( + definition.runtime_name, + FunctionProps( + runtime_definition_name=definition.runtime_name, + runtime_name=definition.runtime_name, + signature=definition.signature, + throws_error=definition.throws_error, + name=definition.name, + description=definition.description, + documentation=definition.documentation, + deprecation_message=definition.deprecation_message, + display_message=definition.display_message, + alias=definition.alias, + display_icon=definition.display_icon, + design=definition.design, + parameters=[ + replace(p, runtime_definition_name=p.runtime_name) + for p in (definition.parameters or []) + ], + ), + ) + + def register_data_type_class(self, klass: type) -> None: + definition = data_type_map(klass) + self.data_types.set(definition.identifier, definition) + + def register_event_class(self, klass: type) -> None: + definition = event_map(klass) + self.events.set(definition.identifier, definition) + + def register_runtime_event_class(self, klass: type) -> None: + omit_definition = get_metadata("hercules:omit_event_definition", klass) or False + definition = runtime_event_map(klass) + self.runtime_events.set(definition.identifier, definition) + if not omit_definition: + self.events.set( + definition.identifier, + EventModel( + identifier=definition.identifier, + signature=definition.signature, + settings=definition.settings, + editable=definition.editable, + name=definition.name, + description=definition.description, + documentation=definition.documentation, + display_message=definition.display_message, + alias=definition.alias, + display_icon=definition.display_icon, + runtime_identifier=definition.identifier, + ), + ) + + # --- runtime dispatch ----------------------------------------------------- + async def send(self, request) -> None: + if self._stream is None: + raise RuntimeError("STREAM_NOT_CONNECTED", "Not connected. Call connect() first.") + await self._stream.write(request) + + async def fire(self, event_class: type, project_id: int, payload: PlainValue) -> None: + if self._stream is None: + raise Exception("Not connected. Call connect() first.") + event_type = get_metadata("hercules:identifier", event_class) + if not event_type: + raise Exception(f"{event_class.__name__} is missing an @Identifier decorator.") + request = action_pb2.ActionTransferRequest( + event=action_pb2.ActionEvent( + project_id=int(project_id), + event_type=event_type, + payload=construct_value(payload if payload is not None else None), + ) + ) + await self.send(request) + self.emit(event_type, project_id, payload) + self.emit(CodeZeroEvent.stream_message_sent, request) + + async def execute_sub_flow(self, sub_flow, *params: PlainValue) -> PlainValue: + if self._stream is None: + raise Exception("Not connected. Call connect() first.") + execution_identifier = sub_flow.execution_identifier + result = self._await_execution_response(execution_identifier) + request = action_pb2.ActionTransferRequest( + sub_flow_execution=action_pb2.ActionSubFlowExecutionRequest( + execution_identifier=execution_identifier, + parameters=[ + construct_value(p if p is not None else None) for p in params + ], + ) + ) + await self.send(request) + self.emit(CodeZeroEvent.stream_message_sent, request) + return await result + + async def execute_flow(self, flow_id, payload: Optional[PlainValue] = None) -> PlainValue: + if self._stream is None: + raise Exception("Not connected. Call connect() first.") + execution_identifier = str(uuid.uuid4()) + result = self._await_execution_response(execution_identifier) + request = action_pb2.ActionTransferRequest( + flow_execution=action_pb2.ActionFlowExecutionRequest( + execution_identifier=execution_identifier, + flow_id=str(flow_id), + payload=construct_value(payload if payload is not None else None), + ) + ) + await self.send(request) + self.emit(CodeZeroEvent.stream_message_sent, request) + return await result + + def _await_execution_response(self, execution_identifier: str) -> asyncio.Future: + future: asyncio.Future = asyncio.get_event_loop().create_future() + self._pending_executions.setdefault(execution_identifier, []).append(future) + return future + + def resolve_execution_response(self, execution_identifier: str, response) -> None: + queue = self._pending_executions.get(execution_identifier) + pending = queue.pop(0) if queue else None + if queue is not None and len(queue) == 0: + del self._pending_executions[execution_identifier] + if pending is None: + self.emit( + CodeZeroEvent.error, + Exception( + f"Received execution response for unknown execution identifier: " + f"{execution_identifier}" + ), + ) + return + which = response.WhichOneof("result") + if which == "success": + pending.set_result(to_allowed_value(response.success)) + elif which == "failure": + pending.set_exception(RuntimeError(response.failure.code, response.failure.message)) + else: + pending.set_exception(Exception("Received execution response with no result")) + + async def connect( + self, + auth_token: str, + aquila_url: Optional[str] = None, + grpc_options: Optional[list] = None, + ) -> None: + if is_export_mode(): + return + url = aquila_url or self._aquila_url + if not url: + raise Exception("aquila_url must be provided in the constructor or connect()") + + try: + channel, stream = await create_connection( + self.build_module(), auth_token, url, grpc_options + ) + self._channel = channel + self._stream = stream + except Exception as err: # noqa: BLE001 + self.emit(CodeZeroEvent.error, err) + raise + + self.emit(CodeZeroEvent.connected, self) + await self._process_stream() + + async def _process_stream(self) -> None: + async for message in self._stream: + self.emit(CodeZeroEvent.stream_message_received, message) + which = message.WhichOneof("data") + if which is None: + self.emit( + CodeZeroEvent.error, + Exception("Received unknown message type from stream"), + ) + continue + handler = self._actions.get(which) + if handler is not None: + handler(self, getattr(message, which)) + + def build_module(self): + return build_module( + ModuleBuildData( + identifier=self._identifier, + version=self._version, + author=self._author, + icon=self._icon, + documentation=self._documentation, + name=self._name, + configuration_definitions=self._configuration_definitions, + data_types=self.data_types.values(), + events=self.events.values(), + runtime_events=self.runtime_events.values(), + functions=self.functions.values(), + runtime_functions=self.runtime_functions.values(), + ) + ) diff --git a/py/hercules/actions/__init__.py b/py/hercules/actions/__init__.py new file mode 100644 index 0000000..abebe3e --- /dev/null +++ b/py/hercules/actions/__init__.py @@ -0,0 +1,21 @@ +"""Action packet handlers (port of ``src/actions/index.ts``).""" +from __future__ import annotations + +from hercules.actions import ( + execution, + flow_execution, + flow_update, + module_configurations, + sub_flow_execution, +) + +# Ordered list of handlers, keyed by their protobuf ``data`` oneof field name. +actions = [ + module_configurations, + execution, + sub_flow_execution, + flow_execution, + flow_update, +] + +__all__ = ["actions"] diff --git a/py/hercules/actions/execution.py b/py/hercules/actions/execution.py new file mode 100644 index 0000000..82134c3 --- /dev/null +++ b/py/hercules/actions/execution.py @@ -0,0 +1,134 @@ +"""Execution request handler (port of ``src/actions/Execution.ts``).""" +from __future__ import annotations + +import asyncio +import inspect +import time + +from hercules._tucana.helpers import construct_value, to_allowed_value +from tucana.generated.aquila import action_pb2 +from tucana.generated.shared import errors_pb2, execution_result_pb2 +from hercules.events import CodeZeroEvent +from hercules.types import ProjectConfiguration, RuntimeError + +packet_type = "execution" + + +def _now_micros() -> int: + return time.time_ns() // 1000 + + +def _build_params(action, execution, func): + params = [] + parameters = func.parameters or [] + for index, _param in enumerate(parameters): + field = execution.parameters[index] if index < len(execution.parameters) else None + which = field.WhichOneof("value") if field is not None else None + if which == "literal_value": + params.append(to_allowed_value(field.literal_value.value)) + elif which == "sub_flow": + sub_flow = field.sub_flow + + def make_caller(sub_flow): + async def caller(*args): + return await action.execute_sub_flow(sub_flow, *args) + + return caller + + params.append(make_caller(sub_flow)) + else: + params.append(None) + return params + + +def _wants_context(handler, param_count: int) -> bool: + try: + sig = inspect.signature(handler) + except (TypeError, ValueError): + return False + positional = [ + p + for p in sig.parameters.values() + if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + ] + # A `*args` handler has no fixed positionals and never gets a context. + return len(positional) == param_count + 1 + + +def handle(action, execution) -> None: + action.emit(CodeZeroEvent.execution_request_received, execution) + + func = action.runtime_functions.get(execution.function_identifier) + if func is None: + print("Received execution request for unknown function:", execution.function_identifier) + return + + params = _build_params(action, execution, func) + + conf = action.configs.get(execution.project_id) or ProjectConfiguration( + project_id=0, + config_values=[], + find_config=lambda identifier: None, + ) + + from hercules.types import FunctionContext + + context = FunctionContext( + project_id=execution.project_id, + execution_id=execution.execution_identifier, + matched_config=conf, + execute_flow=lambda flow_id, payload=None: action.execute_flow(flow_id, payload), + ) + + started_at = _now_micros() + handler = func.handler + all_params = ( + [context, *params] if _wants_context(handler, len(params)) else params + ) + + asyncio.ensure_future(_run(action, execution, handler, all_params, started_at)) + + +async def _run(action, execution, handler, all_params, started_at) -> None: + try: + value = handler(*all_params) + if inspect.isawaitable(value): + value = await value + finished_at = _now_micros() + request = action_pb2.ActionTransferRequest( + result=action_pb2.ActionExecutionResponse( + execution_identifier=execution.execution_identifier, + node_result=execution_result_pb2.NodeExecutionResult( + started_at=started_at, + finished_at=finished_at, + parameter_results=[], + success=construct_value(value if value is not None else None), + ), + ) + ) + await action.send(request) + except Exception as error: # noqa: BLE001 + finished_at = _now_micros() + is_runtime_error = isinstance(error, RuntimeError) + proto_error = errors_pb2.Error( + code=error.code if is_runtime_error else "UNKNOWN_ERROR", + category="RUNTIME", + message=(error.description or str(error)) if is_runtime_error else str(error), + timestamp=finished_at, + version=action.version, + ) + request = action_pb2.ActionTransferRequest( + result=action_pb2.ActionExecutionResponse( + execution_identifier=execution.execution_identifier, + node_result=execution_result_pb2.NodeExecutionResult( + started_at=started_at, + finished_at=finished_at, + parameter_results=[], + error=proto_error, + ), + ) + ) + try: + await action.send(request) + except Exception as err: # noqa: BLE001 + print("Failed to send execution error:", err) diff --git a/py/hercules/actions/flow_execution.py b/py/hercules/actions/flow_execution.py new file mode 100644 index 0000000..1a32ce3 --- /dev/null +++ b/py/hercules/actions/flow_execution.py @@ -0,0 +1,11 @@ +"""Flow execution response handler (port of ``src/actions/FlowExecution.ts``).""" +from __future__ import annotations + +from hercules.events import CodeZeroEvent + +packet_type = "flow_execution_response" + + +def handle(action, response) -> None: + action.emit(CodeZeroEvent.flow_execution_response_received, response) + action.resolve_execution_response(response.execution_identifier, response) diff --git a/py/hercules/actions/flow_update.py b/py/hercules/actions/flow_update.py new file mode 100644 index 0000000..9f1f232 --- /dev/null +++ b/py/hercules/actions/flow_update.py @@ -0,0 +1,18 @@ +"""Flow update handler (port of ``src/actions/FlowUpdate.ts``).""" +from __future__ import annotations + +from hercules.events import CodeZeroEvent + +packet_type = "flow_update" + + +def handle(action, update) -> None: + which = update.WhichOneof("data") + if which == "updated_flow": + flow = update.updated_flow + action.flows.set(flow.flow_id, flow) + action.emit(CodeZeroEvent.flow_updated, flow) + elif which == "deleted_flow": + flow_id = update.deleted_flow + action.flows.delete(flow_id) + action.emit(CodeZeroEvent.flow_deleted, flow_id) diff --git a/py/hercules/actions/module_configurations.py b/py/hercules/actions/module_configurations.py new file mode 100644 index 0000000..d57929a --- /dev/null +++ b/py/hercules/actions/module_configurations.py @@ -0,0 +1,14 @@ +"""Module configurations handler. + +Port of ``src/actions/ModuleConfigurations.ts``. +""" +from __future__ import annotations + +from hercules.events import CodeZeroEvent + +packet_type = "module_configurations" + + +def handle(action, data) -> None: + action.configs.update(data.module_configurations) + action.emit(CodeZeroEvent.module_updated, data) diff --git a/py/hercules/actions/sub_flow_execution.py b/py/hercules/actions/sub_flow_execution.py new file mode 100644 index 0000000..98cb533 --- /dev/null +++ b/py/hercules/actions/sub_flow_execution.py @@ -0,0 +1,14 @@ +"""Sub flow execution response handler. + +Port of ``src/actions/SubFlowExecution.ts``. +""" +from __future__ import annotations + +from hercules.events import CodeZeroEvent + +packet_type = "sub_flow_execution_response" + + +def handle(action, response) -> None: + action.emit(CodeZeroEvent.sub_flow_execution_response_received, response) + action.resolve_execution_response(response.execution_identifier, response) diff --git a/py/hercules/cli.py b/py/hercules/cli.py new file mode 100644 index 0000000..2c46417 --- /dev/null +++ b/py/hercules/cli.py @@ -0,0 +1,137 @@ +"""``hercules`` command line interface (port of ``src/cli.ts``). + +Loads an entry file (without connecting to aquila) and prints the Action as a +JSON document in the shape of the tucana protobuf ``Module`` type. +""" +from __future__ import annotations + +import importlib.util +import os +import sys +from pathlib import Path + +from google.protobuf import json_format + +from hercules.action import Action, registered_actions + +USAGE = """Usage: hercules export [options] + +Loads the given entry file (without connecting to aquila) and prints the +Action as a JSON document in the shape of the tucana protobuf Module type. + +Options: + -o, --out Write the JSON to a file instead of stdout + --compact Emit compact JSON instead of pretty-printed + -h, --help Show this help message + +Examples: + hercules export src/index.py + hercules export app.py -o module.json +""" + + +def _fail(message: str): + sys.stderr.write(f"{message}\n") + raise SystemExit(1) + + +def _parse_export_args(argv): + entry = None + out = None + compact = False + i = 0 + while i < len(argv): + arg = argv[i] + if arg in ("-h", "--help"): + sys.stdout.write(USAGE) + raise SystemExit(0) + elif arg in ("-o", "--out"): + i += 1 + if i >= len(argv): + _fail(f"Missing value for {arg}\n\n{USAGE}") + out = argv[i] + elif arg == "--compact": + compact = True + elif arg.startswith("-"): + _fail(f"Unknown option: {arg}\n\n{USAGE}") + elif entry is None: + entry = arg + else: + _fail(f"Unexpected argument: {arg}\n\n{USAGE}") + i += 1 + if entry is None: + _fail(f"Missing argument\n\n{USAGE}") + return entry, out, compact + + +def _is_action(value) -> bool: + if isinstance(value, Action): + return True + return ( + callable(getattr(value, "build_module", None)) + and isinstance(getattr(value, "identifier", None), str) + and callable(getattr(value, "register_function", None)) + ) + + +def _find_action(module) -> Action: + for value in vars(module).values(): + if _is_action(value): + return value + registered = registered_actions() + if registered: + return registered[-1] + raise Exception( + "No Action found. Define your Action instance in the entry file or " + "construct it during module load." + ) + + +def _load_entry(entry: str): + entry_path = Path(os.getcwd()) / entry + if not entry_path.exists(): + _fail(f"Entry file not found: {entry_path}") + spec = importlib.util.spec_from_file_location("__hercules_entry__", str(entry_path)) + module = importlib.util.module_from_spec(spec) + # Make the entry's own directory importable so relative sibling imports work. + sys.path.insert(0, str(entry_path.parent)) + spec.loader.exec_module(module) + return module + + +def _run_export(argv): + entry, out, compact = _parse_export_args(argv) + # Must be set before the entry file is loaded: it makes connect() a no-op + # and registers constructed Action instances for _find_action. + os.environ["HERCULES_EXPORT"] = "1" + module = _load_entry(entry) + action = _find_action(module) + module_message = action.build_module() + json_text = json_format.MessageToJson( + module_message, + indent=None if compact else 2, + preserving_proto_field_name=False, + ) + json_text = (json_text if compact else json_text) + "\n" + if out: + out_path = Path(os.getcwd()) / out + out_path.write_text(json_text) + sys.stderr.write(f"Module written to {out_path}\n") + else: + sys.stdout.write(json_text) + + +def main(argv=None): + argv = list(sys.argv[1:] if argv is None else argv) + if not argv or argv[0] in ("-h", "--help"): + sys.stdout.write(USAGE) + raise SystemExit(1 if not argv else 0) + command, *rest = argv + if command != "export": + _fail(f"Unknown command: {command}\n\n{USAGE}") + _run_export(rest) + raise SystemExit(0) + + +if __name__ == "__main__": + main() diff --git a/py/hercules/decorators/__init__.py b/py/hercules/decorators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/hercules/decorators/datatype.py b/py/hercules/decorators/datatype.py new file mode 100644 index 0000000..ee37f40 --- /dev/null +++ b/py/hercules/decorators/datatype.py @@ -0,0 +1,39 @@ +"""Data type decorators (port of ``src/decorators/datatype.dec.ts``).""" +from __future__ import annotations + +from typing import Type + +from pydantic import BaseModel + +from hercules._metadata import define_metadata + + +def Schema_(schema: Type[BaseModel]): + """Attach the Pydantic model that describes a data type's shape.""" + + def decorator(target): + define_metadata("hercules:schema", schema, target) + return target + + return decorator + + +def TypeString(type_string: str): + """Emit an explicit TypeScript type string for a data type, verbatim. + + Used by the generated built-in definitions to preserve cross-data-type + identifier references exactly. User-authored data types normally omit this + and let the type be derived from the ``@Schema`` Pydantic model. + """ + + def decorator(target): + define_metadata("hercules:type_string", type_string, target) + return target + + return decorator + + +# Exported under the name ``Schema`` to match the TypeScript decorator. +Schema = Schema_ # type: ignore[assignment] + +__all__ = ["Schema", "TypeString"] diff --git a/py/hercules/decorators/event.py b/py/hercules/decorators/event.py new file mode 100644 index 0000000..739377f --- /dev/null +++ b/py/hercules/decorators/event.py @@ -0,0 +1,35 @@ +"""Event decorators (port of ``src/decorators/event.dec.ts``).""" +from __future__ import annotations + +from typing import Union + +from hercules._metadata import define_metadata, get_own_metadata +from hercules.decorators.meta import normalize_translations +from hercules.models.event import EventSettingProps + + +def _coerce_setting(setting: Union[EventSettingProps, dict]) -> EventSettingProps: + if isinstance(setting, EventSettingProps): + return setting + data = dict(setting) + for key in ("name", "description"): + if data.get(key) is not None: + data[key] = normalize_translations(data[key]) + return EventSettingProps(**data) + + +def EventSetting(setting: Union[EventSettingProps, dict]): + coerced = _coerce_setting(setting) + + def decorator(target): + # get_own_metadata: getMetadata would return the parent class's array on + # subclasses, and inserting would mutate the parent's settings. + settings = get_own_metadata("hercules:flow_settings", target) or [] + settings.insert(0, coerced) + define_metadata("hercules:flow_settings", settings, target) + return target + + return decorator + + +__all__ = ["EventSetting"] diff --git a/py/hercules/decorators/function.py b/py/hercules/decorators/function.py new file mode 100644 index 0000000..acee058 --- /dev/null +++ b/py/hercules/decorators/function.py @@ -0,0 +1,59 @@ +"""Function decorators (port of ``src/decorators/function.dec.ts``).""" +from __future__ import annotations + +from typing import Union + +from hercules._metadata import define_metadata, get_own_metadata +from hercules.decorators.meta import normalize_translations +from hercules.models.function import FunctionParameterProps + + +def OmitFunction(): + def decorator(target): + define_metadata("hercules:omit_function_definition", True, target) + return target + + return decorator + + +def Design(design: str): + def decorator(target): + define_metadata("hercules:design", design, target) + return target + + return decorator + + +def ThrowsError(throws_error: bool = True): + def decorator(target): + define_metadata("hercules:throws_error", throws_error, target) + return target + + return decorator + + +def _coerce_parameter(parameter: Union[FunctionParameterProps, dict]) -> FunctionParameterProps: + if isinstance(parameter, FunctionParameterProps): + return parameter + param = dict(parameter) + for key in ("name", "description", "documentation"): + if param.get(key) is not None: + param[key] = normalize_translations(param[key]) + return FunctionParameterProps(**param) + + +def Parameter(parameter: Union[FunctionParameterProps, dict]): + coerced = _coerce_parameter(parameter) + + def decorator(target): + # get_own_metadata: getMetadata would return the parent class's array on + # subclasses, and inserting would mutate the parent's parameters. + parameters = get_own_metadata("hercules:function_parameters", target) or [] + parameters.insert(0, coerced) + define_metadata("hercules:function_parameters", parameters, target) + return target + + return decorator + + +__all__ = ["OmitFunction", "Design", "ThrowsError", "Parameter"] diff --git a/py/hercules/decorators/meta.py b/py/hercules/decorators/meta.py new file mode 100644 index 0000000..55c5029 --- /dev/null +++ b/py/hercules/decorators/meta.py @@ -0,0 +1,82 @@ +"""Meta decorators (port of ``src/decorators/meta.dec.ts``). + +Each decorator attaches class metadata under a ``hercules:*`` key, mirroring the +TypeScript ``reflect-metadata`` usage. Translation arguments accept either +:class:`~hercules.types.Translation` instances or ``{"code", "content"}`` dicts. +""" +from __future__ import annotations + +from typing import List, Union + +from hercules._metadata import define_metadata +from hercules.types import Translation + +TranslationLike = Union[Translation, dict] + + +def normalize_translations(items) -> List[Translation]: + result: List[Translation] = [] + for item in items: + if isinstance(item, Translation): + result.append(item) + elif isinstance(item, dict): + result.append(Translation(code=item["code"], content=item["content"])) + else: + raise TypeError(f"Invalid translation: {item!r}") + return result + + +def _meta(key: str): + def decorator_factory(*translations: TranslationLike): + normalized = normalize_translations(translations) + + def decorator(target): + define_metadata(key, normalized, target) + return target + + return decorator + + return decorator_factory + + +Identifier = lambda id: _define_simple("hercules:identifier", id) # noqa: E731 +Signature = lambda signature: _define_simple("hercules:signature", signature) # noqa: E731 +DisplayIcon = lambda display_icon: _define_simple("hercules:display_icon", display_icon) # noqa: E731 + + +def _define_simple(key: str, value): + def decorator(target): + define_metadata(key, value, target) + return target + + return decorator + + +def Editable(editable: bool = True): + def decorator(target): + define_metadata("hercules:editable", editable, target) + return target + + return decorator + + +Name = _meta("hercules:name") +Description = _meta("hercules:description") +Documentation = _meta("hercules:documentation") +DisplayMessage = _meta("hercules:display_message") +Alias = _meta("hercules:alias") +DeprecationMessage = _meta("hercules:deprecation_message") + +__all__ = [ + "Identifier", + "Name", + "Description", + "Documentation", + "DisplayMessage", + "Alias", + "DeprecationMessage", + "Signature", + "DisplayIcon", + "Editable", + "normalize_translations", +] diff --git a/py/hercules/decorators/runtime_event.py b/py/hercules/decorators/runtime_event.py new file mode 100644 index 0000000..f7a08d9 --- /dev/null +++ b/py/hercules/decorators/runtime_event.py @@ -0,0 +1,15 @@ +"""Runtime event decorators (port of ``src/decorators/runtime_event.dec.ts``).""" +from __future__ import annotations + +from hercules._metadata import define_metadata + + +def OmitEvent(): + def decorator(target): + define_metadata("hercules:omit_event_definition", True, target) + return target + + return decorator + + +__all__ = ["OmitEvent"] diff --git a/py/hercules/decorators/runtime_function.py b/py/hercules/decorators/runtime_function.py new file mode 100644 index 0000000..4b1bcf4 --- /dev/null +++ b/py/hercules/decorators/runtime_function.py @@ -0,0 +1,18 @@ +"""Runtime function decorators. + +Port of ``src/decorators/runtime_function.dec.ts``. +""" +from __future__ import annotations + +from hercules._metadata import define_metadata + + +def OmitRuntimeFunction(): + def decorator(target): + define_metadata("hercules:omit_function_definition", True, target) + return target + + return decorator + + +__all__ = ["OmitRuntimeFunction"] diff --git a/py/hercules/events.py b/py/hercules/events.py new file mode 100644 index 0000000..1864b4b --- /dev/null +++ b/py/hercules/events.py @@ -0,0 +1,23 @@ +"""Event names (port of ``src/events.ts``).""" +from __future__ import annotations + +from enum import Enum + + +class CodeZeroEvent(str, Enum): + error = "error" + connected = "connected" + stream_message_received = "streamMessageReceived" + stream_message_sent = "streamMessageSent" + module_updated = "moduleUpdated" + execution_request_received = "executionRequestReceived" + sub_flow_execution_response_received = "subFlowExecutionResponseReceived" + flow_execution_response_received = "flowExecutionResponseReceived" + flow_updated = "flowUpdated" + flow_deleted = "flowDeleted" + + def __str__(self) -> str: # so it can be used interchangeably with the value + return self.value + + +__all__ = ["CodeZeroEvent"] diff --git a/py/hercules/internal/__init__.py b/py/hercules/internal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/hercules/internal/connection.py b/py/hercules/internal/connection.py new file mode 100644 index 0000000..9328441 --- /dev/null +++ b/py/hercules/internal/connection.py @@ -0,0 +1,37 @@ +"""gRPC connection (port of ``src/internal/connection.ts``). + +Opens the bi-directional ``ActionTransferService.Transfer`` stream and sends the +initial ``ActionLogon`` request. Uses ``grpc.aio`` for an async streaming client. +""" +from __future__ import annotations + +from typing import Optional, Tuple + +import grpc + +from tucana.generated.aquila import action_pb2, action_pb2_grpc +from tucana.generated.shared import module_pb2 + + +async def create_connection( + module: module_pb2.Module, + auth_token: str, + aquila_url: str, + grpc_options: Optional[list] = None, +) -> Tuple[grpc.aio.Channel, "grpc.aio.StreamStreamCall"]: + channel = grpc.aio.insecure_channel(aquila_url, options=grpc_options) + stub = action_pb2_grpc.ActionTransferServiceStub(channel) + + # Bi-directional stream. Authorization is passed as call metadata. + stream = stub.Transfer(metadata=(("authorization", auth_token),)) + + await stream.write( + action_pb2.ActionTransferRequest( + logon=action_pb2.ActionLogon( + module=module, + scaling_option=action_pb2.ActionLogon.ScalingOption.SPLIT, + ) + ) + ) + + return channel, stream diff --git a/py/hercules/internal/module_builder.py b/py/hercules/internal/module_builder.py new file mode 100644 index 0000000..7d6e340 --- /dev/null +++ b/py/hercules/internal/module_builder.py @@ -0,0 +1,261 @@ +"""Module builder (port of ``src/internal/module-builder.ts``). + +Assembles the tucana ``shared.Module`` protobuf message from the registered +definitions. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional, Union + +from hercules._tucana.helpers import construct_value +from tucana.generated.shared import ( + data_type_pb2, + flow_type_pb2, + function_pb2, + module_pb2, + runtime_flow_type_pb2, + runtime_function_pb2, + translation_pb2, +) +from hercules.models.datatype import DataTypeProps +from hercules.models.event import EventModel +from hercules.models.function import FunctionProps +from hercules.models.runtime_event import RuntimeEventProps +from hercules.models.runtime_function import RuntimeFunctionProps +from hercules.types import ConfigurationDefinition, Translation + +_NONE = flow_type_pb2.FlowTypeSetting.UniquenessScope.NONE + + +@dataclass +class ModuleBuildData: + identifier: str + version: str + author: str + icon: str + documentation: str + name: List[Translation] + configuration_definitions: List[ConfigurationDefinition] + data_types: List[DataTypeProps] + events: List[EventModel] + runtime_events: List[RuntimeEventProps] + functions: List[FunctionProps] + runtime_functions: List[RuntimeFunctionProps] + + +def _translations(items) -> List[translation_pb2.Translation]: + result = [] + for item in items or []: + if isinstance(item, Translation): + result.append(translation_pb2.Translation(code=item.code, content=item.content)) + elif isinstance(item, dict): + result.append( + translation_pb2.Translation(code=item["code"], content=item["content"]) + ) + else: + result.append(translation_pb2.Translation(code=item.code, content=item.content)) + return result + + +def _to_uniqueness_scope(unique: Optional[Union[int, str]]) -> int: + if unique is None: + return _NONE + if isinstance(unique, str): + scope = flow_type_pb2.FlowTypeSetting.UniquenessScope.Value(unique) + return scope + return unique + + +def _config_def(def_: ConfigurationDefinition, version: str) -> module_pb2.ModuleConfigurationDefinition: + kwargs = dict( + identifier=def_.identifier, + name=_translations(def_.name), + description=_translations(def_.description), + type=def_.type, + linked_data_type_identifiers=def_.linked_data_types or [], + optional=def_.optional or False, + hidden=def_.hidden or False, + ) + if def_.default_value is not None: + kwargs["default_value"] = construct_value(def_.default_value) + return module_pb2.ModuleConfigurationDefinition(**kwargs) + + +def _data_type(dt: DataTypeProps, version: str) -> data_type_pb2.DefinitionDataType: + return data_type_pb2.DefinitionDataType( + identifier=dt.identifier, + name=_translations(dt.name), + alias=_translations(dt.alias), + rules=list(dt.rules or []), + generic_keys=list(dt.generic_keys or []), + type=dt.type, + linked_data_type_identifiers=[], + display_message=_translations(dt.display_message), + version=version, + definition_source="action", + ) + + +def _flow_type_setting(s, version: str) -> flow_type_pb2.FlowTypeSetting: + kwargs = dict( + identifier=s.identifier, + unique=_to_uniqueness_scope(s.unique), + name=_translations(s.name), + description=_translations(s.description), + optional=s.optional or False, + hidden=s.hidden or False, + ) + if s.default_value is not None: + kwargs["default_value"] = construct_value(s.default_value) + return flow_type_pb2.FlowTypeSetting(**kwargs) + + +def _flow_type(ft: EventModel, version: str) -> flow_type_pb2.FlowType: + return flow_type_pb2.FlowType( + identifier=ft.identifier, + settings=[_flow_type_setting(s, version) for s in (ft.settings or [])], + editable=ft.editable or False, + name=_translations(ft.name), + description=_translations(ft.description), + documentation=_translations(ft.documentation), + display_message=_translations(ft.display_message), + alias=_translations(ft.alias), + version=version, + display_icon=ft.display_icon or "tabler:note", + definition_source="action", + linked_data_type_identifiers=[], + signature=ft.signature, + runtime_identifier=ft.runtime_identifier or ft.identifier, + ) + + +def _runtime_flow_type_setting(s, version: str) -> runtime_flow_type_pb2.RuntimeFlowTypeSetting: + kwargs = dict( + identifier=s.identifier, + unique=_to_uniqueness_scope(s.unique), + name=_translations(s.name), + description=_translations(s.description), + optional=s.optional or False, + hidden=s.hidden or False, + ) + if s.default_value is not None: + kwargs["default_value"] = construct_value(s.default_value) + return runtime_flow_type_pb2.RuntimeFlowTypeSetting(**kwargs) + + +def _runtime_flow_type(rft: RuntimeEventProps, version: str) -> runtime_flow_type_pb2.RuntimeFlowType: + return runtime_flow_type_pb2.RuntimeFlowType( + identifier=rft.identifier, + runtime_settings=[_runtime_flow_type_setting(s, version) for s in (rft.settings or [])], + editable=rft.editable or False, + name=_translations(rft.name), + description=_translations(rft.description), + documentation=_translations(rft.documentation), + display_message=_translations(rft.display_message), + alias=_translations(rft.alias), + version=version, + display_icon=rft.display_icon or "tabler:note", + definition_source="action", + linked_data_type_identifiers=[], + signature=rft.signature, + ) + + +def _parameter_definition(p, version: str) -> function_pb2.ParameterDefinition: + kwargs = dict( + runtime_name=p.runtime_name, + runtime_definition_name=p.runtime_definition_name or p.runtime_name, + name=_translations(p.name), + description=_translations(p.description), + documentation=_translations(p.documentation), + hidden=p.hidden or False, + optional=p.optional or False, + ) + if p.default_value is not None: + kwargs["default_value"] = construct_value(p.default_value) + return function_pb2.ParameterDefinition(**kwargs) + + +def _function_definition(f: FunctionProps, version: str) -> function_pb2.FunctionDefinition: + kwargs = dict( + runtime_name=f.runtime_name, + runtime_definition_name=f.runtime_definition_name, + signature=f.signature, + throws_error=f.throws_error or False, + name=_translations(f.name), + description=_translations(f.description), + documentation=_translations(f.documentation), + deprecation_message=_translations(f.deprecation_message), + display_message=_translations(f.display_message), + alias=_translations(f.alias), + linked_data_type_identifiers=[], + display_icon=f.display_icon or "tabler:note", + version=version, + definition_source="action", + parameter_definitions=[_parameter_definition(p, version) for p in (f.parameters or [])], + ) + if f.design is not None: + kwargs["design"] = f.design + return function_pb2.FunctionDefinition(**kwargs) + + +def _runtime_parameter_definition(p, version: str) -> runtime_function_pb2.RuntimeParameterDefinition: + kwargs = dict( + runtime_name=p.runtime_name, + name=_translations(p.name), + description=_translations(p.description), + documentation=_translations(p.documentation), + hidden=p.hidden or False, + optional=p.optional or False, + ) + if p.default_value is not None: + kwargs["default_value"] = construct_value(p.default_value) + return runtime_function_pb2.RuntimeParameterDefinition(**kwargs) + + +def _runtime_function_definition(f: RuntimeFunctionProps, version: str) -> runtime_function_pb2.RuntimeFunctionDefinition: + kwargs = dict( + runtime_name=f.runtime_name, + signature=f.signature, + throws_error=f.throws_error or False, + name=_translations(f.name), + description=_translations(f.description), + documentation=_translations(f.documentation), + deprecation_message=_translations(f.deprecation_message), + display_message=_translations(f.display_message), + alias=_translations(f.alias), + linked_data_type_identifiers=[], + display_icon=f.display_icon or "tabler:note", + version=version, + definition_source="action", + runtime_parameter_definitions=[ + _runtime_parameter_definition(p, version) for p in (f.parameters or []) + ], + ) + if f.design is not None: + kwargs["design"] = f.design + return runtime_function_pb2.RuntimeFunctionDefinition(**kwargs) + + +def build_module(data: ModuleBuildData) -> module_pb2.Module: + version = data.version + return module_pb2.Module( + identifier=data.identifier, + version=version, + author=data.author, + icon=data.icon, + documentation=data.documentation, + name=_translations(data.name), + description=[], + definition_source="action", + configurations=[_config_def(c, version) for c in data.configuration_definitions], + definition_data_types=[_data_type(dt, version) for dt in data.data_types], + flow_types=[_flow_type(ft, version) for ft in data.events], + runtime_flow_types=[_runtime_flow_type(rft, version) for rft in data.runtime_events], + function_definitions=[_function_definition(f, version) for f in data.functions], + runtime_function_definitions=[ + _runtime_function_definition(f, version) for f in data.runtime_functions + ], + definitions=[], + ) diff --git a/py/hercules/internal/pydantic_codegen.py b/py/hercules/internal/pydantic_codegen.py new file mode 100644 index 0000000..ddb0a7f --- /dev/null +++ b/py/hercules/internal/pydantic_codegen.py @@ -0,0 +1,82 @@ +"""TypeScript type string -> Pydantic v2 model code. + +Pipeline: + + TS type expression --(Node: ts-json-schema-generator)--> JSON schema + --(datamodel-code-generator)--------> Pydantic v2 code + +``datamodel-code-generator`` does not parse TypeScript itself, so the TypeScript +is first turned into a JSON schema by the bundled Node helper +(:mod:`hercules.internal.tsgen`). + +Custom/referenced types that appear in the TypeScript (other data types) can be +declared up front via ``preamble`` so the generator resolves them, e.g. +``preamble="type CustomType = unknown;"``. +""" +from __future__ import annotations + +import json +import shutil +import subprocess +import tempfile +from pathlib import Path +from typing import Optional + +from hercules.internal.tsgen import ts_to_json_schema + + +class PydanticCodegenError(RuntimeError): + """Raised when Pydantic code generation fails.""" + + +def _datamodel_codegen() -> str: + exe = shutil.which("datamodel-codegen") + if exe is None: + raise PydanticCodegenError( + "datamodel-code-generator is not installed. Install it with " + "'pip install datamodel-code-generator' (or the 'codegen' extra)." + ) + return exe + + +def ts_to_pydantic( + ts_type: str, + name: str, + *, + preamble: Optional[str] = None, + snake_case_fields: bool = False, +) -> str: + """Convert a TypeScript type expression into Pydantic v2 model source code. + + ``name`` becomes the root model's class name. Nested object types become + their own models; unions and custom references are preserved. + """ + schema = ts_to_json_schema(ts_type, name, preamble) + + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + in_file = tmp_path / "schema.json" + out_file = tmp_path / "model.py" + in_file.write_text(json.dumps(schema)) + + cmd = [ + _datamodel_codegen(), + "--input", str(in_file), + "--input-file-type", "jsonschema", + "--output", str(out_file), + "--output-model-type", "pydantic_v2.BaseModel", + "--class-name", name, + "--use-standard-collections", + "--use-union-operator", + "--disable-timestamp", + "--formatters", "black", "isort", + ] + if snake_case_fields: + cmd.append("--snake-case-field") + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + raise PydanticCodegenError( + result.stderr.strip() or "datamodel-code-generator failed." + ) + return out_file.read_text() diff --git a/py/hercules/internal/schema.py b/py/hercules/internal/schema.py new file mode 100644 index 0000000..ad4ad1a --- /dev/null +++ b/py/hercules/internal/schema.py @@ -0,0 +1,158 @@ +"""Schema registry plus TypeScript type-string and rule generation. + +Data types are described with Pydantic models. Registered models are keyed by +class; when a registered model appears nested inside another type it is rendered +as its identifier instead of being inlined, which lets recursive and +mutually-recursive data types resolve by reference (mirroring the TypeScript SDK's +``zod-schema`` behaviour). + +The Pydantic ``model_json_schema()`` output is pre-processed here (references to +registered models become ``tsType`` identifier hints, non-registered nested models +are inlined, titles are stripped) and then handed to the Node helper, which turns +it into an inline TypeScript type expression. +""" +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Type + +from pydantic import BaseModel + +from tucana.generated.shared import data_type_pb2 +from hercules.internal.tsgen import json_schema_to_ts + +SchemaModel = Type[BaseModel] + +# Maps a registered Pydantic model class -> its data-type identifier. +_registry: Dict[SchemaModel, str] = {} + + +def register_schema(model: SchemaModel, identifier: str) -> None: + _registry[model] = identifier + + +def get_registered_identifier(model: SchemaModel) -> Optional[str]: + return _registry.get(model) + + +def _registered_names() -> Dict[str, str]: + """Map each registered model's JSON-schema definition name to its identifier.""" + return {model.__name__: identifier for model, identifier in _registry.items()} + + +# JSON-schema annotation keywords that describe rather than shape a type. They +# would otherwise surface as JSDoc comments / noise in the emitted type string. +_ANNOTATION_KEYS = ("title", "description", "default", "examples", "$comment") + + +def _strip_annotations(node: Any) -> None: + if isinstance(node, dict): + for key in _ANNOTATION_KEYS: + node.pop(key, None) + for value in node.values(): + _strip_annotations(value) + elif isinstance(node, list): + for item in node: + _strip_annotations(item) + + +def _resolve( + node: Any, + defs: Dict[str, Any], + names: Dict[str, str], + self_name: str, + self_identifier: Optional[str], + stack: List[str], +) -> Any: + """Recursively rewrite ``$ref`` nodes. + + References to registered models (and self-references) become ``tsType`` + identifier hints; references to non-registered nested models are inlined so + the whole type collapses into a single expression. A non-registered recursive + reference cannot be inlined and raises. + """ + if isinstance(node, list): + return [ + _resolve(item, defs, names, self_name, self_identifier, stack) + for item in node + ] + if not isinstance(node, dict): + return node + + ref = node.get("$ref") + if isinstance(ref, str) and ref.startswith("#/$defs/"): + ref_name = ref.split("/")[-1] + if ref_name in names: + return {"tsType": names[ref_name]} + if ref_name == self_name and self_identifier is not None: + return {"tsType": self_identifier} + if ref_name in stack: + subject = ( + f'data type "{self_identifier}"' if self_identifier else "the schema" + ) + raise ValueError( + f"Cannot generate a type string for {subject}: it contains " + f"recursive schemas that cannot be inlined. Register each " + f"recursive schema as its own data type so it can be referenced " + f"by identifier." + ) + target = defs.get(ref_name, {}) + return _resolve( + target, defs, names, self_name, self_identifier, stack + [ref_name] + ) + + result: Dict[str, Any] = {} + for key, value in node.items(): + if key in _ANNOTATION_KEYS: + continue + result[key] = _resolve( + value, defs, names, self_name, self_identifier, stack + ) + return result + + +def pydantic_to_type_string(model: SchemaModel) -> str: + schema = model.model_json_schema() + defs = schema.pop("$defs", {}) + + self_identifier = _registry.get(model) + names = _registered_names() + + ref = schema.get("$ref") + if isinstance(ref, str) and ref.startswith("#/$defs/"): + # Self-recursive models put the body under ``$defs`` and make the root a ref. + self_name = ref.split("/")[-1] + body: Any = defs.get(self_name, {}) + else: + self_name = schema.get("title") or model.__name__ + body = schema + + resolved = _resolve(body, defs, names, self_name, self_identifier, [self_name]) + _strip_annotations(resolved) + + name = self_identifier or model.__name__ + return json_schema_to_ts(resolved, name) + + +def pydantic_to_rules(model: SchemaModel) -> List[data_type_pb2.DefinitionDataTypeRule]: + schema = model.model_json_schema() + rules: List[data_type_pb2.DefinitionDataTypeRule] = [] + + pattern = schema.get("pattern") + if pattern: + rules.append( + data_type_pb2.DefinitionDataTypeRule( + regex=data_type_pb2.DataTypeRegexRuleConfig(pattern=pattern) + ) + ) + + minimum = schema.get("minimum") + maximum = schema.get("maximum") + if minimum is not None and maximum is not None: + rules.append( + data_type_pb2.DefinitionDataTypeRule( + number_range=data_type_pb2.DataTypeNumberRangeRuleConfig( + **{"from": int(minimum), "to": int(maximum)} + ) + ) + ) + return rules diff --git a/py/hercules/internal/tsgen.py b/py/hercules/internal/tsgen.py new file mode 100644 index 0000000..4a4be8f --- /dev/null +++ b/py/hercules/internal/tsgen.py @@ -0,0 +1,93 @@ +"""Bridge to the bundled Node schema-conversion helper (``hercules/_tsgen``). + +The helper handles the two directions that have no good pure-Python equivalent: + +* a Pydantic JSON schema -> an inline TypeScript type expression + (via ``json-schema-to-typescript``) +* a TypeScript type expression -> a JSON schema + (via ``ts-json-schema-generator``) + +Node must be installed. The helper's own npm dependencies are installed lazily on +first use if ``node_modules`` is missing. +""" +from __future__ import annotations + +import json +import shutil +import subprocess +from functools import lru_cache +from pathlib import Path +from typing import Any, Dict, Optional + +_TSGEN_DIR = Path(__file__).resolve().parent.parent / "_tsgen" +_CONVERT = _TSGEN_DIR / "convert.mjs" + + +class TsGenError(RuntimeError): + """Raised when the Node schema-conversion helper cannot run or fails.""" + + +@lru_cache(maxsize=1) +def _node() -> str: + node = shutil.which("node") + if node is None: + raise TsGenError( + "Node.js is required for TypeScript <-> Pydantic schema conversion " + "but 'node' was not found on PATH. Install Node.js to continue." + ) + return node + + +@lru_cache(maxsize=1) +def _ensure_dependencies() -> None: + if (_TSGEN_DIR / "node_modules").is_dir(): + return + npm = shutil.which("npm") + if npm is None: + raise TsGenError( + f"The Node helper dependencies are not installed and 'npm' was not " + f"found on PATH. Run 'npm install' in {_TSGEN_DIR} manually." + ) + result = subprocess.run( + [npm, "install"], + cwd=_TSGEN_DIR, + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise TsGenError( + f"Failed to install Node helper dependencies in {_TSGEN_DIR}:\n" + f"{result.stderr.strip()}" + ) + + +def _run(request: Dict[str, Any]) -> Dict[str, Any]: + _ensure_dependencies() + result = subprocess.run( + [_node(), str(_CONVERT)], + input=json.dumps(request), + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise TsGenError(result.stderr.strip() or "Node helper failed.") + return json.loads(result.stdout) + + +def json_schema_to_ts(schema: Dict[str, Any], name: str) -> str: + """Compile a (pre-processed) JSON schema into an inline TypeScript type string.""" + return _run({"mode": "json_schema_to_ts", "schema": schema, "name": name})["type"] + + +def ts_to_json_schema( + ts_type: str, name: str, preamble: Optional[str] = None +) -> Dict[str, Any]: + """Convert a TypeScript type expression into a JSON schema.""" + request: Dict[str, Any] = { + "mode": "ts_to_json_schema", + "ts": ts_type, + "name": name, + } + if preamble: + request["preamble"] = preamble + return _run(request)["schema"] diff --git a/py/hercules/manager/__init__.py b/py/hercules/manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/hercules/manager/base.py b/py/hercules/manager/base.py new file mode 100644 index 0000000..01c54cb --- /dev/null +++ b/py/hercules/manager/base.py @@ -0,0 +1,73 @@ +"""Base manager (port of ``src/manager/BaseManager.ts``).""" +from __future__ import annotations + +from typing import Callable, Dict, Generic, Iterator, List, Optional, Tuple, TypeVar + +K = TypeVar("K") +V = TypeVar("V") +T = TypeVar("T") + + +class BaseManager(Generic[K, V]): + def __init__(self) -> None: + self.cache: Dict[K, V] = {} + + @property + def size(self) -> int: + return len(self.cache) + + def get(self, key: K) -> Optional[V]: + return self.cache.get(key) + + def has(self, key: K) -> bool: + return key in self.cache + + def set(self, key: K, value: V) -> "BaseManager[K, V]": + self.cache[key] = value + return self + + def delete(self, key: K) -> bool: + if key in self.cache: + del self.cache[key] + return True + return False + + def clear(self) -> None: + self.cache.clear() + + def values(self) -> List[V]: + return list(self.cache.values()) + + def keys(self) -> List[K]: + return list(self.cache.keys()) + + def entries(self) -> List[Tuple[K, V]]: + return list(self.cache.items()) + + def find(self, predicate: Callable[[V, K], bool]) -> Optional[V]: + for key, value in self.cache.items(): + if predicate(value, key): + return value + return None + + def filter(self, predicate: Callable[[V, K], bool]) -> List[V]: + return [value for key, value in self.cache.items() if predicate(value, key)] + + def map(self, fn: Callable[[V, K], T]) -> List[T]: + return [fn(value, key) for key, value in self.cache.items()] + + def some(self, predicate: Callable[[V, K], bool]) -> bool: + return any(predicate(value, key) for key, value in self.cache.items()) + + def every(self, predicate: Callable[[V, K], bool]) -> bool: + return all(predicate(value, key) for key, value in self.cache.items()) + + def for_each(self, fn: Callable[[V, K], None]) -> None: + for key, value in self.cache.items(): + fn(value, key) + + def __iter__(self) -> Iterator[Tuple[K, V]]: + return iter(self.cache.items()) + + def __len__(self) -> int: + return len(self.cache) diff --git a/py/hercules/manager/config.py b/py/hercules/manager/config.py new file mode 100644 index 0000000..2ed281f --- /dev/null +++ b/py/hercules/manager/config.py @@ -0,0 +1,45 @@ +"""Config manager (port of ``src/manager/config-manager.ts``).""" +from __future__ import annotations + +from typing import List + +from hercules._tucana.helpers import construct_value, to_allowed_value +from hercules.manager.base import BaseManager +from hercules.types import ProjectConfiguration + + +class ConfigManager(BaseManager): + def update(self, configs: List) -> None: + self.clear() + for config in configs: + module_configurations = list(config.module_configurations) + + def make_find_config(mcs): + def find_config(identifier): + mc = next( + (c for c in mcs if c.identifier == identifier), None + ) + if mc is None: + return None + return to_allowed_value( + mc.value if mc.HasField("value") else construct_value(None) + ) + + return find_config + + self.set( + config.project_id, + ProjectConfiguration( + project_id=config.project_id, + config_values=[ + { + "identifier": mc.identifier, + "value": to_allowed_value( + mc.value if mc.HasField("value") else construct_value(None) + ), + } + for mc in module_configurations + ], + find_config=make_find_config(module_configurations), + ), + ) diff --git a/py/hercules/manager/datatype.py b/py/hercules/manager/datatype.py new file mode 100644 index 0000000..3775c53 --- /dev/null +++ b/py/hercules/manager/datatype.py @@ -0,0 +1,8 @@ +"""Data type manager (port of ``src/manager/DataTypeManager.ts``).""" +from __future__ import annotations + +from hercules.manager.base import BaseManager + + +class DataTypeManager(BaseManager): + pass diff --git a/py/hercules/manager/event.py b/py/hercules/manager/event.py new file mode 100644 index 0000000..8e8fc72 --- /dev/null +++ b/py/hercules/manager/event.py @@ -0,0 +1,8 @@ +"""Event manager (port of ``src/manager/EventManager.ts``).""" +from __future__ import annotations + +from hercules.manager.base import BaseManager + + +class EventManager(BaseManager): + pass diff --git a/py/hercules/manager/flow.py b/py/hercules/manager/flow.py new file mode 100644 index 0000000..048f0fb --- /dev/null +++ b/py/hercules/manager/flow.py @@ -0,0 +1,12 @@ +"""Flow manager (port of ``src/manager/FlowManager.ts``). + +Registry of the action's own flows, kept in sync via ActionFlowUpdate messages. +Keyed by flow id. +""" +from __future__ import annotations + +from hercules.manager.base import BaseManager + + +class FlowManager(BaseManager): + pass diff --git a/py/hercules/manager/function.py b/py/hercules/manager/function.py new file mode 100644 index 0000000..dc7d258 --- /dev/null +++ b/py/hercules/manager/function.py @@ -0,0 +1,8 @@ +"""Function manager (port of ``src/manager/FunctionManager.ts``).""" +from __future__ import annotations + +from hercules.manager.base import BaseManager + + +class FunctionManager(BaseManager): + pass diff --git a/py/hercules/manager/runtime_event.py b/py/hercules/manager/runtime_event.py new file mode 100644 index 0000000..8d90d73 --- /dev/null +++ b/py/hercules/manager/runtime_event.py @@ -0,0 +1,8 @@ +"""Runtime event manager (port of ``src/manager/RuntimeEventManager.ts``).""" +from __future__ import annotations + +from hercules.manager.base import BaseManager + + +class RuntimeEventManager(BaseManager): + pass diff --git a/py/hercules/manager/runtime_function.py b/py/hercules/manager/runtime_function.py new file mode 100644 index 0000000..bc3de96 --- /dev/null +++ b/py/hercules/manager/runtime_function.py @@ -0,0 +1,8 @@ +"""Runtime function manager (port of ``src/manager/RuntimeFunctionManager.ts``).""" +from __future__ import annotations + +from hercules.manager.base import BaseManager + + +class RuntimeFunctionManager(BaseManager): + pass diff --git a/py/hercules/map/__init__.py b/py/hercules/map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/hercules/map/datatype.py b/py/hercules/map/datatype.py new file mode 100644 index 0000000..df8ca43 --- /dev/null +++ b/py/hercules/map/datatype.py @@ -0,0 +1,41 @@ +"""Data type mapping (port of ``src/map/datatype.map.ts``).""" +from __future__ import annotations + +from hercules._metadata import get_metadata +from hercules.internal.schema import register_schema +from hercules.models.datatype import DataTypeProps + + +def data_type_map(klass: type) -> DataTypeProps: + identifier = get_metadata("hercules:identifier", klass) + name = get_metadata("hercules:name", klass) or [] + display_message = get_metadata("hercules:display_message", klass) or [] + alias = get_metadata("hercules:alias", klass) or [] + generic_keys = get_metadata("hercules:generic_keys", klass) or [] + + if not identifier: + raise ValueError( + f"Data type class {klass.__name__} is missing an identifier. " + f'Add @Identifier("your_identifier") to the class.' + ) + + schema = get_metadata("hercules:schema", klass) + if schema is None: + raise ValueError( + f"Data type class {klass.__name__} is missing a schema. " + f"Add @Schema(MyModel) with a Pydantic model to the class." + ) + + register_schema(schema, identifier) + + type_string = get_metadata("hercules:type_string", klass) + + return DataTypeProps( + identifier=identifier, + schema=schema, + name=name, + display_message=display_message, + alias=alias, + generic_keys=generic_keys, + type_string=type_string, + ) diff --git a/py/hercules/map/event.py b/py/hercules/map/event.py new file mode 100644 index 0000000..d492f74 --- /dev/null +++ b/py/hercules/map/event.py @@ -0,0 +1,74 @@ +"""Event mapping (port of ``src/map/event.map.ts``).""" +from __future__ import annotations + +from dataclasses import fields, replace +from typing import List + +from hercules._metadata import get_metadata, get_own_metadata +from hercules.map.runtime_event import runtime_event_map +from hercules.models.event import EventModel, EventSettingProps + + +def _merge_setting(base: EventSettingProps, override: EventSettingProps) -> EventSettingProps: + # Mirror `{...rs, ...override}`: only the properties the override actually set + # (non-None) replace the runtime event's setting. + changes = { + f.name: getattr(override, f.name) + for f in fields(override) + if f.name != "identifier" and getattr(override, f.name) is not None + } + return replace(base, **changes) + + +def event_map(klass: type) -> EventModel: + parent_class = klass.__bases__[0] + runtime_event = runtime_event_map(parent_class) + + identifier = get_metadata("hercules:identifier", klass) + signature = get_metadata("hercules:signature", klass) + settings: List[EventSettingProps] = ( + get_own_metadata("hercules:flow_settings", klass) or [] + ) + name = get_metadata("hercules:name", klass) + description = get_metadata("hercules:description", klass) + documentation = get_metadata("hercules:documentation", klass) + display_message = get_metadata("hercules:display_message", klass) + alias = get_metadata("hercules:alias", klass) + display_icon = get_metadata("hercules:display_icon", klass) + editable = get_metadata("hercules:editable", klass) + editable = editable if editable is not None else False + + if not identifier: + raise ValueError( + f"Event class {klass.__name__} is missing an identifier. " + f'Add @Identifier("your_identifier") to the class.' + ) + + runtime_settings = runtime_event.settings or [] + for es in settings: + if not any(s.identifier == es.identifier for s in runtime_settings): + raise ValueError( + f'Event class {klass.__name__} has a setting "{es.identifier}" ' + f"that does not exist in its runtime event." + ) + + # Settings come from the runtime event (in its order); the event class only + # overrides individual properties per identifier. + merged_settings: List[EventSettingProps] = [] + for rs in runtime_settings: + override = next((s for s in settings if s.identifier == rs.identifier), None) + merged_settings.append(_merge_setting(rs, override) if override else replace(rs)) + + return EventModel( + runtime_identifier=runtime_event.identifier, + identifier=identifier, + signature=signature or runtime_event.signature, + settings=merged_settings, + name=name or runtime_event.name, + description=description or runtime_event.description, + documentation=documentation or runtime_event.documentation, + display_message=display_message or runtime_event.display_message, + alias=alias or runtime_event.alias, + display_icon=display_icon or runtime_event.display_icon, + editable=editable, + ) diff --git a/py/hercules/map/function.py b/py/hercules/map/function.py new file mode 100644 index 0000000..3c7555a --- /dev/null +++ b/py/hercules/map/function.py @@ -0,0 +1,76 @@ +"""Function mapping (port of ``src/map/function.map.ts``).""" +from __future__ import annotations + +from dataclasses import replace +from typing import List + +from hercules._metadata import get_metadata +from hercules.map.runtime_function import runtime_function_map +from hercules.models.function import FunctionParameterProps, FunctionProps + + +def function_map(klass: type) -> FunctionProps: + parent_class = klass.__bases__[0] + runtime_function = runtime_function_map(parent_class) + + identifier = get_metadata("hercules:identifier", klass) + function_parameters: List[FunctionParameterProps] = ( + get_metadata("hercules:function_parameters", klass) or [] + ) + name = get_metadata("hercules:name", klass) + display_message = get_metadata("hercules:display_message", klass) + description = get_metadata("hercules:description", klass) + deprecation_message = get_metadata("hercules:deprecation_message", klass) + alias = get_metadata("hercules:alias", klass) + documentation = get_metadata("hercules:documentation", klass) + signature = get_metadata("hercules:signature", klass) + display_icon = get_metadata("hercules:display_icon", klass) + throws_error = get_metadata("hercules:throws_error", klass) + design = get_metadata("hercules:design", klass) + + runtime_params = runtime_function.parameters or [] + if len(function_parameters) > len(runtime_params): + raise ValueError( + f"Function definition class {klass.__name__} has more function " + f"parameters than its runtime function." + ) + + for fp in function_parameters: + if not any(p.runtime_name == fp.runtime_name for p in runtime_params): + raise ValueError( + f"Function definition class {klass.__name__} has a function " + f'parameter "{fp.runtime_name}" that does not exist in its ' + f"runtime function." + ) + + merged: List[FunctionParameterProps] = list(function_parameters) + for rp in runtime_params: + if not any(p.runtime_name == rp.runtime_name for p in merged): + merged.append(replace(rp, runtime_definition_name=rp.runtime_name)) + + return FunctionProps( + runtime_definition_name=runtime_function.runtime_name, + runtime_name=identifier or runtime_function.runtime_name, + signature=signature or runtime_function.signature, + throws_error=throws_error if throws_error is not None else runtime_function.throws_error, + alias=alias or runtime_function.alias, + description=description or runtime_function.description, + name=name or runtime_function.name, + documentation=documentation or runtime_function.documentation, + deprecation_message=deprecation_message or runtime_function.deprecation_message, + display_message=display_message or runtime_function.display_message, + display_icon=display_icon or runtime_function.display_icon, + design=design or runtime_function.design, + parameters=[ + replace( + p, + runtime_definition_name=p.runtime_definition_name or p.runtime_name, + name=p.name or [], + description=p.description or [], + documentation=p.documentation or [], + hidden=p.hidden or False, + optional=p.optional or False, + ) + for p in merged + ], + ) diff --git a/py/hercules/map/runtime_event.py b/py/hercules/map/runtime_event.py new file mode 100644 index 0000000..526fa33 --- /dev/null +++ b/py/hercules/map/runtime_event.py @@ -0,0 +1,43 @@ +"""Runtime event mapping (port of ``src/map/runtime_event.map.ts``).""" +from __future__ import annotations + +from hercules._metadata import get_metadata +from hercules.models.runtime_event import RuntimeEventProps + + +def runtime_event_map(klass: type) -> RuntimeEventProps: + identifier = get_metadata("hercules:identifier", klass) + signature = get_metadata("hercules:signature", klass) + settings = get_metadata("hercules:flow_settings", klass) or [] + name = get_metadata("hercules:name", klass) or [] + description = get_metadata("hercules:description", klass) or [] + documentation = get_metadata("hercules:documentation", klass) or [] + display_message = get_metadata("hercules:display_message", klass) or [] + alias = get_metadata("hercules:alias", klass) or [] + display_icon = get_metadata("hercules:display_icon", klass) + editable = get_metadata("hercules:editable", klass) + editable = editable if editable is not None else False + + if not identifier: + raise ValueError( + f"Runtime event class {klass.__name__} is missing an identifier. " + f'Add @Identifier("your_identifier") to the class.' + ) + if not signature: + raise ValueError( + f"Runtime event class {klass.__name__} is missing a signature. " + f'Add @Signature("(): RETURN_TYPE") to the class.' + ) + + return RuntimeEventProps( + identifier=identifier, + signature=signature, + settings=settings, + name=name, + description=description, + documentation=documentation, + display_message=display_message, + alias=alias, + display_icon=display_icon, + editable=editable, + ) diff --git a/py/hercules/map/runtime_function.py b/py/hercules/map/runtime_function.py new file mode 100644 index 0000000..e71b42d --- /dev/null +++ b/py/hercules/map/runtime_function.py @@ -0,0 +1,64 @@ +"""Runtime function mapping (port of ``src/map/runtime_function.map.ts``).""" +from __future__ import annotations + +from dataclasses import replace + +from hercules._metadata import get_metadata +from hercules.models.function import FunctionParameterProps +from hercules.models.runtime_function import RuntimeFunctionProps + + +def _fill_parameter(param: FunctionParameterProps) -> FunctionParameterProps: + return replace( + param, + name=param.name or [], + description=param.description or [], + documentation=param.documentation or [], + hidden=param.hidden or False, + optional=param.optional or False, + ) + + +def runtime_function_map(klass: type) -> RuntimeFunctionProps: + identifier = get_metadata("hercules:identifier", klass) + runtime_parameters = get_metadata("hercules:function_parameters", klass) or [] + name = get_metadata("hercules:name", klass) or [] + display_message = get_metadata("hercules:display_message", klass) or [] + description = get_metadata("hercules:description", klass) or [] + deprecation_message = get_metadata("hercules:deprecation_message", klass) or [] + alias = get_metadata("hercules:alias", klass) or [] + documentation = get_metadata("hercules:documentation", klass) or [] + signature = get_metadata("hercules:signature", klass) + display_icon = get_metadata("hercules:display_icon", klass) or "" + throws_error = get_metadata("hercules:throws_error", klass) or False + design = get_metadata("hercules:design", klass) + + if not identifier: + raise ValueError( + f"Runtime function class {klass.__name__} is missing an identifier. " + f'Add @Identifier("your_identifier") to the class.' + ) + if not signature: + raise ValueError( + f"Runtime function class {klass.__name__} is missing a signature. " + f'Add @Signature("(param1: TYPE_1): RETURN_TYPE") to the class.' + ) + + instance = klass() + handler = instance.run + + return RuntimeFunctionProps( + runtime_name=identifier, + signature=signature, + throws_error=throws_error, + name=name, + description=description, + documentation=documentation, + deprecation_message=deprecation_message, + display_message=display_message, + alias=alias, + display_icon=display_icon or "tabler:note", + design=design, + parameters=[_fill_parameter(p) for p in runtime_parameters], + handler=handler, + ) diff --git a/py/hercules/models/__init__.py b/py/hercules/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/hercules/models/datatype.py b/py/hercules/models/datatype.py new file mode 100644 index 0000000..a459981 --- /dev/null +++ b/py/hercules/models/datatype.py @@ -0,0 +1,49 @@ +"""Data type models (port of ``src/models/datatype.model.ts``).""" +from __future__ import annotations + +from typing import List, Optional + +from hercules.schema import Schema +from hercules.internal.schema import pydantic_to_rules, pydantic_to_type_string +from hercules.types import Translation + + +class DataTypeProps: + """A resolved data type definition. + + ``type`` is computed lazily from the schema so that errors (e.g. a recursive + schema that cannot be inlined) surface only when the type string is read. + """ + + def __init__( + self, + identifier: str, + schema: Schema, + name: Optional[List[Translation]] = None, + display_message: Optional[List[Translation]] = None, + alias: Optional[List[Translation]] = None, + generic_keys: Optional[List[str]] = None, + type_string: Optional[str] = None, + ) -> None: + self.identifier = identifier + self.schema = schema + self.name = name or [] + self.display_message = display_message or [] + self.alias = alias or [] + self.generic_keys = generic_keys or [] + # ``type_string`` is an explicit TypeScript type to emit verbatim. The + # generated built-in definitions use it to keep cross-data-type identifier + # references byte-exact; user-authored data types leave it unset and the + # type is derived from the Pydantic schema instead. + self._type_string = type_string + self.rules = pydantic_to_rules(schema) + + @property + def type(self) -> str: + if self._type_string is not None: + return self._type_string + return pydantic_to_type_string(self.schema) + + +class DataTypeRunnable: + """Base class for data type definitions.""" diff --git a/py/hercules/models/event.py b/py/hercules/models/event.py new file mode 100644 index 0000000..9c8fccf --- /dev/null +++ b/py/hercules/models/event.py @@ -0,0 +1,35 @@ +"""Event (flow type) models (port of ``src/models/event.model.ts``).""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional, Union + +from hercules._tucana.helpers import PlainValue +from hercules.types import FlowTypeSetting_UniquenessScope, Translation + + +@dataclass +class EventSettingProps: + identifier: str + unique: Optional[Union[int, str]] = None + linked_data_type_identifiers: Optional[List[str]] = None + default_value: Optional[PlainValue] = None + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + optional: Optional[bool] = None + hidden: Optional[bool] = None + + +@dataclass +class EventModel: + identifier: str + signature: str + settings: Optional[List[EventSettingProps]] = None + editable: Optional[bool] = None + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + documentation: Optional[List[Translation]] = None + display_message: Optional[List[Translation]] = None + alias: Optional[List[Translation]] = None + display_icon: Optional[str] = None + runtime_identifier: Optional[str] = None diff --git a/py/hercules/models/function.py b/py/hercules/models/function.py new file mode 100644 index 0000000..2b341f6 --- /dev/null +++ b/py/hercules/models/function.py @@ -0,0 +1,37 @@ +"""Function definition models (port of ``src/models/function.model.ts``).""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List, Optional + +from hercules._tucana.helpers import PlainValue +from hercules.types import Translation + + +@dataclass +class FunctionParameterProps: + runtime_name: str + default_value: Optional[PlainValue] = None + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + documentation: Optional[List[Translation]] = None + hidden: Optional[bool] = None + optional: Optional[bool] = None + runtime_definition_name: Optional[str] = None + + +@dataclass +class FunctionProps: + runtime_definition_name: str + runtime_name: str + signature: str + parameters: Optional[List[FunctionParameterProps]] = None + throws_error: Optional[bool] = None + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + documentation: Optional[List[Translation]] = None + deprecation_message: Optional[List[Translation]] = None + display_message: Optional[List[Translation]] = None + alias: Optional[List[Translation]] = None + display_icon: Optional[str] = None + design: Optional[str] = None diff --git a/py/hercules/models/runtime_event.py b/py/hercules/models/runtime_event.py new file mode 100644 index 0000000..4c31c61 --- /dev/null +++ b/py/hercules/models/runtime_event.py @@ -0,0 +1,29 @@ +"""Runtime event (runtime flow type) models. + +Port of ``src/models/runtime_event.model.ts``. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + +from hercules.models.event import EventSettingProps +from hercules.types import Translation + + +class RuntimeEventRunnable: + """Base class for runtime events (runtime flow types).""" + + +@dataclass +class RuntimeEventProps: + identifier: str + signature: str + settings: Optional[List[EventSettingProps]] = None + editable: Optional[bool] = None + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + documentation: Optional[List[Translation]] = None + display_message: Optional[List[Translation]] = None + alias: Optional[List[Translation]] = None + display_icon: Optional[str] = None diff --git a/py/hercules/models/runtime_function.py b/py/hercules/models/runtime_function.py new file mode 100644 index 0000000..7612a94 --- /dev/null +++ b/py/hercules/models/runtime_function.py @@ -0,0 +1,33 @@ +"""Runtime function models (port of ``src/models/runtime_function.model.ts``).""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable, List, Optional + +from hercules._tucana.helpers import PlainValue +from hercules.models.function import FunctionParameterProps +from hercules.types import Translation + + +class RuntimeFunctionRunnable: + """Base class for runtime functions. Subclasses implement ``run``.""" + + def run(self, *args): # pragma: no cover - overridden by subclasses + raise NotImplementedError + + +@dataclass +class RuntimeFunctionProps: + runtime_name: str + signature: str + handler: Callable[..., PlainValue] + parameters: Optional[List[FunctionParameterProps]] = None + throws_error: Optional[bool] = None + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + documentation: Optional[List[Translation]] = None + deprecation_message: Optional[List[Translation]] = None + display_message: Optional[List[Translation]] = None + alias: Optional[List[Translation]] = None + display_icon: Optional[str] = None + design: Optional[str] = None diff --git a/py/hercules/schema.py b/py/hercules/schema.py new file mode 100644 index 0000000..7d0e81f --- /dev/null +++ b/py/hercules/schema.py @@ -0,0 +1,38 @@ +"""Data-type schema surface, built on Pydantic v2. + +Data types are described with plain Pydantic models. Object shapes are ordinary +``BaseModel`` subclasses; scalar/primitive shapes (a bare ``number``, a +constrained ``string``, ``Record``, ...) use ``RootModel``. The +model is turned into a TypeScript type string and validation rules by +:mod:`hercules.internal.schema`. + +Example:: + + from hercules.schema import BaseModel, RootModel, Annotated, StringConstraints + + class Email(RootModel[Annotated[str, StringConstraints(pattern=r"^[^@]+@[^@]+$")]]): + pass + + class User(BaseModel): + name: str + email: Email +""" +from __future__ import annotations + +from typing import Type + +from typing_extensions import Annotated +from pydantic import BaseModel, RootModel, Field, StringConstraints, ConfigDict + +# A data-type schema is any Pydantic model class (``RootModel`` is a ``BaseModel``). +Schema = Type[BaseModel] + +__all__ = [ + "Schema", + "BaseModel", + "RootModel", + "Field", + "StringConstraints", + "ConfigDict", + "Annotated", +] diff --git a/py/hercules/types.py b/py/hercules/types.py new file mode 100644 index 0000000..1ce5bc1 --- /dev/null +++ b/py/hercules/types.py @@ -0,0 +1,75 @@ +"""Shared types for the hercules SDK (port of ``src/types.ts``).""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Awaitable, Callable, List, Optional, Union + +from hercules._tucana.helpers import PlainValue +from tucana.generated.shared.flow_type_pb2 import FlowTypeSetting +from tucana.generated.shared.runtime_flow_type_pb2 import RuntimeFlowTypeSetting + +# Re-export the uniqueness scope enums (mirror the TS re-exports). +FlowTypeSetting_UniquenessScope = FlowTypeSetting.UniquenessScope +RuntimeFlowTypeSetting_UniquenessScope = RuntimeFlowTypeSetting.UniquenessScope + +__all__ = [ + "PlainValue", + "FlowTypeSetting_UniquenessScope", + "RuntimeFlowTypeSetting_UniquenessScope", + "Translation", + "SubFlow", + "FunctionContext", + "ProjectConfiguration", + "ConfigurationDefinition", + "RuntimeError", +] + + +@dataclass +class Translation: + code: str + content: str + + +# A sub flow is an awaitable callable resolving to a plain value. +SubFlow = Callable[..., Awaitable[PlainValue]] + + +@dataclass +class ProjectConfiguration: + project_id: int + config_values: List[dict] = field(default_factory=list) + find_config: Callable[[str], Optional[PlainValue]] = lambda identifier: None + + +@dataclass +class FunctionContext: + project_id: int + execution_id: str + matched_config: ProjectConfiguration + execute_flow: Callable[..., Awaitable[PlainValue]] + + +@dataclass +class ConfigurationDefinition: + identifier: str + type: str + name: Optional[List[Translation]] = None + description: Optional[List[Translation]] = None + hidden: Optional[bool] = None + optional: Optional[bool] = None + linked_data_types: Optional[List[str]] = None + default_value: Optional[PlainValue] = None + + +class RuntimeError(Exception): + """Raised (or reported) when a runtime function fails with a known code.""" + + def __init__(self, code: str, description: Optional[str] = None) -> None: + message = f"Runtime error with code {code} occurred." + ( + f" Description: {description}" if description else "" + ) + super().__init__(message) + self.name = "RuntimeErrorException" + self.code = code + self.description = description From 34425828adff155a80fd515ca0732cac6baf3f23 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 15:00:15 +0200 Subject: [PATCH 05/12] feat: add .gitignore file to exclude Python environment and cache files --- py/examples/simple-example-py/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 py/examples/simple-example-py/.gitignore diff --git a/py/examples/simple-example-py/.gitignore b/py/examples/simple-example-py/.gitignore new file mode 100644 index 0000000..a14fb0f --- /dev/null +++ b/py/examples/simple-example-py/.gitignore @@ -0,0 +1,5 @@ +.venv/ +__pycache__/ +*.py[cod] +.env +module.json From 96987c5675a897af4e68d4a49f6a4d22b5149394 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 15:00:27 +0200 Subject: [PATCH 06/12] feat: add initial project structure with example action and runtime functions --- py/examples/simple-example-py/.python-version | 1 + py/examples/simple-example-py/README.md | 38 ++ .../simple-example-py/data_types/__init__.py | 0 .../data_types/email_data_type.py | 13 + .../simple-example-py/events/__init__.py | 0 .../events/user_created_runtime_event.py | 15 + py/examples/simple-example-py/example.env | 4 + .../simple-example-py/functions/__init__.py | 0 .../functions/fibonacci_function.py | 19 + .../functions/fibonacci_runtime_function.py | 27 ++ .../functions/for_each_runtime_function.py | 41 ++ py/examples/simple-example-py/index.py | 75 +++ py/examples/simple-example-py/pyproject.toml | 19 + py/examples/simple-example-py/uv.lock | 429 ++++++++++++++++++ 14 files changed, 681 insertions(+) create mode 100644 py/examples/simple-example-py/.python-version create mode 100644 py/examples/simple-example-py/README.md create mode 100644 py/examples/simple-example-py/data_types/__init__.py create mode 100644 py/examples/simple-example-py/data_types/email_data_type.py create mode 100644 py/examples/simple-example-py/events/__init__.py create mode 100644 py/examples/simple-example-py/events/user_created_runtime_event.py create mode 100644 py/examples/simple-example-py/example.env create mode 100644 py/examples/simple-example-py/functions/__init__.py create mode 100644 py/examples/simple-example-py/functions/fibonacci_function.py create mode 100644 py/examples/simple-example-py/functions/fibonacci_runtime_function.py create mode 100644 py/examples/simple-example-py/functions/for_each_runtime_function.py create mode 100644 py/examples/simple-example-py/index.py create mode 100644 py/examples/simple-example-py/pyproject.toml create mode 100644 py/examples/simple-example-py/uv.lock diff --git a/py/examples/simple-example-py/.python-version b/py/examples/simple-example-py/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/py/examples/simple-example-py/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/py/examples/simple-example-py/README.md b/py/examples/simple-example-py/README.md new file mode 100644 index 0000000..692f4af --- /dev/null +++ b/py/examples/simple-example-py/README.md @@ -0,0 +1,38 @@ +# simple-example-py + +A minimal example action built with the Python `hercules` SDK. + +This is a self-contained [uv](https://docs.astral.sh/uv/) project that depends on +the SDK from the local checkout (`../..`), mirroring the TypeScript example's +`"@code0-tech/hercules": "file:../.."` setup. + +## Setup + +```bash +uv sync # creates .venv and installs hercules (editable) + deps +``` + +## Run + +```bash +uv run python index.py +``` + +Environment variables (see `example.env`): `ACTION_ID`, `VERSION`, `AQUILA_URL`, +`AUTH_TOKEN`. + +## Export the action as a Module JSON + +Without connecting to aquila: + +```bash +uv run hercules export index.py # print to stdout +uv run hercules export index.py -o module.json +uv run hercules export index.py --compact +``` + +## Notes + +- Requires **Python 3.10+** (pinned to 3.12 via `.python-version`). +- The SDK converts data-type schemas to TypeScript via a bundled Node helper, so + **Node.js** must be installed for data types to build. diff --git a/py/examples/simple-example-py/data_types/__init__.py b/py/examples/simple-example-py/data_types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/examples/simple-example-py/data_types/email_data_type.py b/py/examples/simple-example-py/data_types/email_data_type.py new file mode 100644 index 0000000..3720cb4 --- /dev/null +++ b/py/examples/simple-example-py/data_types/email_data_type.py @@ -0,0 +1,13 @@ +from hercules import Identifier, Name, Schema +from hercules.schema import RootModel, Annotated, StringConstraints + + +class EmailModel(RootModel[Annotated[str, StringConstraints(pattern=r"^[^@]+@[^@]+\.[^@]+$")]]): + """An email address, described as a constrained string.""" + + +@Identifier("email_address") +@Name({"code": "en-US", "content": "Email Address"}) +@Schema(EmailModel) +class EmailDataType: + pass diff --git a/py/examples/simple-example-py/events/__init__.py b/py/examples/simple-example-py/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/examples/simple-example-py/events/user_created_runtime_event.py b/py/examples/simple-example-py/events/user_created_runtime_event.py new file mode 100644 index 0000000..1919a39 --- /dev/null +++ b/py/examples/simple-example-py/events/user_created_runtime_event.py @@ -0,0 +1,15 @@ +from hercules import Description, DisplayMessage, Identifier, Name, Signature + + +@Identifier("user_created") +@Signature("(): {userId: number}") +@Name({"code": "en-US", "content": "User created event"}) +@DisplayMessage({"code": "en-US", "content": "Triggers on user creation"}) +@Description( + { + "code": "en-US", + "content": "Triggers on user creation and has a payload including the user database id", + } +) +class UserCreatedRuntimeEvent: + pass diff --git a/py/examples/simple-example-py/example.env b/py/examples/simple-example-py/example.env new file mode 100644 index 0000000..c08a669 --- /dev/null +++ b/py/examples/simple-example-py/example.env @@ -0,0 +1,4 @@ +ACTION_ID=testing-action +VERSION=0.0.0 +AQUILA_URL=127.0.0.1:8081 +AUTH_TOKEN=your_auth_token_here diff --git a/py/examples/simple-example-py/functions/__init__.py b/py/examples/simple-example-py/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/examples/simple-example-py/functions/fibonacci_function.py b/py/examples/simple-example-py/functions/fibonacci_function.py new file mode 100644 index 0000000..7b6a8f0 --- /dev/null +++ b/py/examples/simple-example-py/functions/fibonacci_function.py @@ -0,0 +1,19 @@ +from hercules import Identifier, Name, Parameter + +from functions.fibonacci_runtime_function import FibonacciRuntimeFunction + + +@Identifier("fibonacci") +@Name({"code": "en-US", "content": "Compute Fibonacci Number"}) +@Parameter( + { + "runtime_name": "test", + "name": [{"code": "en-US", "content": "Input Number"}], + "description": [ + {"code": "en-US", "content": "The position in the Fibonacci sequence"} + ], + "default_value": 10, + } +) +class FibonacciFunction(FibonacciRuntimeFunction): + pass diff --git a/py/examples/simple-example-py/functions/fibonacci_runtime_function.py b/py/examples/simple-example-py/functions/fibonacci_runtime_function.py new file mode 100644 index 0000000..3b15f69 --- /dev/null +++ b/py/examples/simple-example-py/functions/fibonacci_runtime_function.py @@ -0,0 +1,27 @@ +from hercules import ( + DisplayMessage, + FunctionContext, + Identifier, + Name, + OmitRuntimeFunction, + Parameter, + RuntimeFunctionRunnable, + Signature, +) + + +@Identifier("fibonacci_runtime") +@Signature("(test: number): number") +@Name({"code": "en-US", "content": "Fibonacci (Runtime)"}) +@DisplayMessage({"code": "en-US", "content": "Computes the n-th Fibonacci number"}) +@OmitRuntimeFunction() +@Parameter({"runtime_name": "test", "name": [{"code": "en-US", "content": "N"}]}) +class FibonacciRuntimeFunction(RuntimeFunctionRunnable): + def run(self, context: FunctionContext, test): + print(f"[fibonacci] project={context.project_id} execution={context.execution_id}") + return self._fib(test) + + def _fib(self, n): + if n <= 1: + return n + return self._fib(n - 1) + self._fib(n - 2) diff --git a/py/examples/simple-example-py/functions/for_each_runtime_function.py b/py/examples/simple-example-py/functions/for_each_runtime_function.py new file mode 100644 index 0000000..b521f28 --- /dev/null +++ b/py/examples/simple-example-py/functions/for_each_runtime_function.py @@ -0,0 +1,41 @@ +from hercules import ( + DisplayMessage, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeFunctionRunnable, + Signature, +) + + +@Identifier("for_each_runtime") +@Signature("(list: LIST, consumer: CONSUMER): void") +@Name({"code": "en-US", "content": "For Each"}) +@DisplayMessage({"code": "en-US", "content": "For each element of ${list} do ${consumer}"}) +@Parameter( + { + "runtime_name": "list", + "name": [{"code": "en-US", "content": "List"}], + "description": [ + {"code": "en-US", "content": "The list whose elements are iterated over"} + ], + } +) +@Parameter( + { + "runtime_name": "consumer", + "name": [{"code": "en-US", "content": "Consumer"}], + "description": [ + { + "code": "en-US", + "content": "The sub flow (item) => void executed once per element", + } + ], + } +) +class ForEachRuntimeFunction(RuntimeFunctionRunnable): + async def run(self, context: FunctionContext, items, consumer): + for element in items: + result = await consumer(element) + print("[for_each] sub flow result:", result) diff --git a/py/examples/simple-example-py/index.py b/py/examples/simple-example-py/index.py new file mode 100644 index 0000000..847175f --- /dev/null +++ b/py/examples/simple-example-py/index.py @@ -0,0 +1,75 @@ +import asyncio +import os + +from hercules import Action, CodeZeroEvent + +from data_types.email_data_type import EmailDataType +from events.user_created_runtime_event import UserCreatedRuntimeEvent +from functions.fibonacci_function import FibonacciFunction +from functions.fibonacci_runtime_function import FibonacciRuntimeFunction +from functions.for_each_runtime_function import ForEachRuntimeFunction + +action = Action( + os.environ.get("ACTION_ID", "testing-action"), + os.environ.get("VERSION", "0.0.0"), + os.environ.get("AQUILA_URL", "127.0.0.1:8081"), + "code0-tech", + "tabler:bolt", + "A simple example action", + [{"code": "en-US", "content": "Example Action"}], + [ + { + "identifier": "EXAMPLE_CONFIG", + "type": "string", + "name": [{"code": "en-US", "content": "Example Config"}], + } + ], +) + +# Runtime function (with OmitRuntimeFunction — no auto-generated function def). +action.register_runtime_function(FibonacciRuntimeFunction) + +# Function: named public variant that extends the runtime function. +action.register_function(FibonacciFunction) + +# Runtime function that executes a sub flow parameter for each element of a list. +action.register_runtime_function(ForEachRuntimeFunction) + +# Data type: derived from a schema. +action.register_data_type_class(EmailDataType) + +# Runtime flow type: the internal event definition. +action.register_runtime_event_class(UserCreatedRuntimeEvent) + + +action.on(CodeZeroEvent.connected, lambda _action: print("Connected to aquila")) + + +def _on_error(error): + print("Stream error:", error) + + +action.on(CodeZeroEvent.error, _on_error) + + +def _on_module_updated(message): + print(message) + print(action.configs.values()) + + +action.on(CodeZeroEvent.module_updated, _on_module_updated) + + +async def main(): + auth_token = os.environ.get("AUTH_TOKEN", "your_auth_token_here") + while True: + try: + await action.connect(auth_token) + except Exception as err: # noqa: BLE001 + print("Stream error:", err) + print("Attempting to reconnect in 5s...") + await asyncio.sleep(5) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/py/examples/simple-example-py/pyproject.toml b/py/examples/simple-example-py/pyproject.toml new file mode 100644 index 0000000..b5487d7 --- /dev/null +++ b/py/examples/simple-example-py/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "simple-example-py" +version = "0.0.0" +description = "A minimal example action built with the Python hercules SDK." +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "code0-hercules", +] + +# Depend on the hercules SDK from the local checkout (two levels up), mirroring +# the TypeScript example's `"@code0-tech/hercules": "file:../.."` dependency. +[tool.uv.sources] +code0-hercules = { path = "../..", editable = true } + +[dependency-groups] +dev = [ + "pytest>=7.4", +] diff --git a/py/examples/simple-example-py/uv.lock b/py/examples/simple-example-py/uv.lock new file mode 100644 index 0000000..ac4e237 --- /dev/null +++ b/py/examples/simple-example-py/uv.lock @@ -0,0 +1,429 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "code0-hercules" +version = "0.0.0" +source = { editable = "../../" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "tucana" }, +] + +[package.metadata] +requires-dist = [ + { name = "datamodel-code-generator", marker = "extra == 'codegen'", specifier = ">=0.25" }, + { name = "grpcio", specifier = ">=1.64" }, + { name = "protobuf", specifier = ">=5.0" }, + { name = "pydantic", specifier = ">=2" }, + { name = "tucana", specifier = ">=0.0.80" }, +] +provides-extras = ["codegen"] + +[package.metadata.requires-dev] +dev = [ + { name = "datamodel-code-generator", specifier = ">=0.25" }, + { name = "pytest", specifier = ">=7.4" }, + { name = "pytest-asyncio", specifier = ">=0.23" }, +] + +[[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 = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "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 = "grpcio" +version = "1.83.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/fd/655c8a773d728bc3c93fb4713ae4bf79ffc75996f86fb78b2974c8e1dfbd/grpcio-1.83.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8", size = 6334247, upload-time = "2026-07-23T15:18:53.099Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9a/1ce5760d35a04a992006dd2f79afff2db548f93ee7426fa95c9f1fc90c61/grpcio-1.83.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727", size = 12168650, upload-time = "2026-07-23T15:18:56.348Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ab/bbcb5be0a1a6cb21f036e2afdd4f7a70147cfb7a7b42648a310d7c43acfc/grpcio-1.83.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5882c1a721b50ce0123ee5e839e1ab059ad72a7ade76cdf2d5bd833b56791acf", size = 6916899, upload-time = "2026-07-23T15:18:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d2/4c27977ecb3b3f9f363b93f570e001cb24ef264a9a907d7fd0f949ed59f0/grpcio-1.83.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e3eedfc92b6b9f2960115e7e620cf0cbf80bb7849a51ce3820dc54dfd88b6b9", size = 7648761, upload-time = "2026-07-23T15:19:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/23/49/0c823a7627ff2e69a61e4a53c4edf215272892fc2c47c6431f033d46f4cc/grpcio-1.83.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fcaa7c45c45b4a89e2867d1f1785d9481a788399d915e341ed2eb49aeef9dd4", size = 7074920, upload-time = "2026-07-23T15:19:02.293Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ce/963f01ff7c789a76909c9691b704112e02ca1e11c10405cd99c2bd7c40f1/grpcio-1.83.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6b6c666a1d5613ff360c9e90f44665e3a88b25a815209ddbc0917eec281931cb", size = 7598046, upload-time = "2026-07-23T15:19:03.921Z" }, + { url = "https://files.pythonhosted.org/packages/eb/de/1ce6bdefc847a7973040d10cebc8996c653a2a687c0a4da8d05dcab4e397/grpcio-1.83.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6be5c807b717be3dd649446f021301fd7907e376318675d2147823071034112a", size = 8634792, upload-time = "2026-07-23T15:19:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8b/7fe6a73895e3bdd788101d1276e48e0d262ebb165afacec1ec4efebcd785/grpcio-1.83.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c834e86d8fd2f03d7e4db49a027f7c5b89c5b88eed305543a5295bd6fee61e40", size = 8000286, upload-time = "2026-07-23T15:19:07.739Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b0/9a779de2bcda8722501a056fad1bec3d1117977af0c080ab1fc0655fdf35/grpcio-1.83.0-cp310-cp310-win32.whl", hash = "sha256:35a5b1c192496b6c25956eebfa963468935612206fd2543ac3ce981e6a5e0f03", size = 4404616, upload-time = "2026-07-23T15:19:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8e/ce9a23590cac33a6c24e6386cc0ffc55821cc13212acc822e98f00a67161/grpcio-1.83.0-cp310-cp310-win_amd64.whl", hash = "sha256:8f6c395e493d20c39b29392ca200e9aaeb78d0bc2f04db0c0a7da7ddc939aa57", size = 5162304, upload-time = "2026-07-23T15:19:11.467Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/a1735f215b2a5cd43c38b79eac072ad197e61be9829905b6b29550abd0db/grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf", size = 7613472, upload-time = "2026-07-23T15:19:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/c9e81f806ac704b6b145cb01628db398985b1f8dfdc10e23b55fb0902b3d/grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd", size = 7040616, upload-time = "2026-07-23T15:19:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ba/94cd5af859876049d340480acbb61a959096c84b567f215534faa78d0424/grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c", size = 7570491, upload-time = "2026-07-23T15:19:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/108d30d5a5c964312ae8b9cb0e8cc5b3c1cc68d8f757cca52b3565534d26/grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5", size = 8605036, upload-time = "2026-07-23T15:19:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/3828ae13c3db8233d123ad612747665817b952d8a954f32390230b582336/grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5", size = 7981587, upload-time = "2026-07-23T15:19:48.913Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/77af31228f55f55a2a5112bb0077ad0a1c4d23dbb0c2853a62475bbdcc14/grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc", size = 4394004, upload-time = "2026-07-23T15:19:50.618Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/f706e39550e7a3732ce2b9c5926107a93d74a802775b19b642a6df27dc96/grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df", size = 5158525, upload-time = "2026-07-23T15:19:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" }, + { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/9c/60/f2cca8147ea213d3e43ae9158d03ad04e020fdf32ff027253e1fe93f921d/grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa", size = 6305607, upload-time = "2026-07-23T15:20:15.353Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ab/d3874931d123a95e83a3ebf8aa04537988fb62425cedb8bf3cefc5ad41b2/grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c", size = 12166617, upload-time = "2026-07-23T15:20:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/92/ff/6f18f9426b69306f4e00a9add3b0ee2748da8aad53836ef80cab0d62d04f/grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b", size = 6880213, upload-time = "2026-07-23T15:20:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/70/21/706d1147c6b93b98f179240c13991fbcc56880eba0c868abb1ad40d8a0a6/grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c", size = 7618335, upload-time = "2026-07-23T15:20:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/1a8443c889115ec9e213a213e86bc93a71ee9088027e5befa09aaa0edd9d/grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df", size = 7043416, upload-time = "2026-07-23T15:20:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/c6/94e0fee5b12bc1da1370185b680988db6f739d19b42d9959db01a7ea50bf/grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b", size = 7583253, upload-time = "2026-07-23T15:20:26.313Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/de1ccb671fb85575bc5192faedf9ecdbdf5b390d2e6584dcf552bcbd370e/grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404", size = 8605102, upload-time = "2026-07-23T15:20:28.437Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/0e0ec749a7034ffcbaa050e39779872950ead90c22e7e0116be3f28b2b46/grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af", size = 7979826, upload-time = "2026-07-23T15:20:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/c3fda157287f64bc65acee6c5aa90c41acf9e0d3a8e69a265eecff6d00a1/grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33", size = 4471765, upload-time = "2026-07-23T15:20:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/b1b26431c9d54eee11724fd6e5585473a2ed47fbc1fb95e5204906a642ce/grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9", size = 5298932, upload-time = "2026-07-23T15:20:35.48Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +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 = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +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 = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "simple-example-py" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "code0-hercules" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "code0-hercules", editable = "../../" }] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=7.4" }] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tucana" +version = "0.0.80" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/56/a409c44d53bf4f63a77d6c0d9e5dadaac3a59cfa02e89f3b00cfb85849a5/tucana-0.0.80.tar.gz", hash = "sha256:b249fbb505306d1ffd77fbdc6ac769bae25a82a9c9e2776d5062ea1b7bce1b2f", size = 21834, upload-time = "2026-08-04T14:09:37.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/8f/cdcf8e5d6c520ceca0816a83df7dfb9b8d3dcf5cb697c269f88c21b9e373/tucana-0.0.80-py3-none-any.whl", hash = "sha256:6c6331d31cb59455e7c0839720273eb6347daed093f502dbd73e0b3b81e40fda", size = 66111, upload-time = "2026-08-04T14:09:37.98Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] From d602ddfca1d732ce87bb1765591ed3cc98d2a6f9 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 15:00:33 +0200 Subject: [PATCH 07/12] feat: add unit tests for Pydantic-based data types and export functionality --- py/tests/test_datatype.py | 121 ++++++++++++++++++++++++++++++++++++++ py/tests/test_export.py | 55 +++++++++++++++++ py/tests/test_index.py | 87 +++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 py/tests/test_datatype.py create mode 100644 py/tests/test_export.py create mode 100644 py/tests/test_index.py diff --git a/py/tests/test_datatype.py b/py/tests/test_datatype.py new file mode 100644 index 0000000..2425ffe --- /dev/null +++ b/py/tests/test_datatype.py @@ -0,0 +1,121 @@ +"""Port of ``ts/test/datatype.test.ts`` — Pydantic-based data types.""" +from typing import List, Optional + +import pytest + +from hercules import Identifier, Schema +from hercules.schema import BaseModel +from hercules.map.datatype import data_type_map + + +def test_inlines_plain_schemas(): + class Plain(BaseModel): + name: str + + @Identifier("PlainType") + @Schema(Plain) + class PlainType: + pass + + assert data_type_map(PlainType).type == "{ name: string; }" + + +def test_resolves_mutually_recursive_schemas_by_identifier(): + class Order(BaseModel): + addresses: List["Address"] + + class Address(BaseModel): + order: "Order" + + Order.model_rebuild() + Address.model_rebuild() + + @Identifier("MutualOrder") + @Schema(Order) + class MutualOrder: + pass + + @Identifier("MutualAddress") + @Schema(Address) + class MutualAddress: + pass + + order_def = data_type_map(MutualOrder) + address_def = data_type_map(MutualAddress) + + assert order_def.type == "{ addresses: MutualAddress[]; }" + assert address_def.type == "{ order: MutualOrder; }" + + +def test_references_itself_for_self_recursive_schemas(): + class Node(BaseModel): + children: List["Node"] + + Node.model_rebuild() + + @Identifier("TreeNode") + @Schema(Node) + class TreeNode: + pass + + assert data_type_map(TreeNode).type == "{ children: TreeNode[]; }" + + +def test_prints_registered_schemas_as_identifier_references(): + class Item(BaseModel): + related: List["Item"] + + Item.model_rebuild() + + @Identifier("EmbeddedItem") + @Schema(Item) + class EmbeddedItem: + pass + + class Payload(BaseModel): + item: Item + count: int + + @Identifier("ItemPayload") + @Schema(Payload) + class ItemPayload: + pass + + data_type_map(EmbeddedItem) + payload_def = data_type_map(ItemPayload) + + assert payload_def.type == "{ item: EmbeddedItem; count: number; }" + + +def test_throws_for_unregistered_recursive_schema(): + class Loop(BaseModel): + next: Optional["Loop"] = None + + Loop.model_rebuild() + + class Broken(BaseModel): + loop: Loop + + @Identifier("BrokenPayload") + @Schema(Broken) + class BrokenPayload: + pass + + definition = data_type_map(BrokenPayload) + with pytest.raises(ValueError, match=r"BrokenPayload.*recursive"): + _ = definition.type + + +def test_type_string_override_is_used_verbatim(): + from hercules import TypeString + + class Ignored(BaseModel): + whatever: str + + @Identifier("OVERRIDDEN") + @TypeString("{ a: number; b: EMAIL; }") + @Schema(Ignored) + class Overridden: + pass + + assert data_type_map(Overridden).type == "{ a: number; b: EMAIL; }" diff --git a/py/tests/test_export.py b/py/tests/test_export.py new file mode 100644 index 0000000..7eee816 --- /dev/null +++ b/py/tests/test_export.py @@ -0,0 +1,55 @@ +"""Port of ``ts/test/export.test.ts``.""" +import asyncio +import os + +import pytest +from google.protobuf import json_format + +from hercules import Action, registered_actions +from tucana.generated.shared import module_pb2 + + +def create_action(identifier="test-action"): + return Action( + identifier, + "1.2.3", + None, + "code0-tech", + "tabler:bolt", + "docs", + [{"code": "en-US", "content": "Test Action"}], + ) + + +@pytest.fixture(autouse=True) +def _clear_export_env(): + yield + os.environ.pop("HERCULES_EXPORT", None) + + +def test_build_module_is_serializable_as_protobuf_json(): + action = create_action() + json_text = json_format.MessageToJson(action.build_module()) + roundtrip = json_format.Parse(json_text, module_pb2.Module()) + assert roundtrip.identifier == "test-action" + assert roundtrip.version == "1.2.3" + assert roundtrip.author == "code0-tech" + + +def test_registers_constructed_actions_when_export_set(): + os.environ["HERCULES_EXPORT"] = "1" + action = create_action("registered-action") + assert registered_actions()[-1] is action + + +def test_does_not_register_without_export(): + before = len(registered_actions()) + create_action("unregistered-action") + assert len(registered_actions()) == before + + +def test_connect_is_noop_in_export_mode(): + os.environ["HERCULES_EXPORT"] = "1" + action = create_action() + assert asyncio.get_event_loop().run_until_complete(action.connect("token")) is None + assert action.stream is None diff --git a/py/tests/test_index.py b/py/tests/test_index.py new file mode 100644 index 0000000..ef98bcd --- /dev/null +++ b/py/tests/test_index.py @@ -0,0 +1,87 @@ +"""Port of ``ts/test/index.test.ts``.""" +from hercules import EventSetting, Identifier, Parameter +from hercules._metadata import get_metadata +from hercules.map.event import event_map +from hercules.map.runtime_event import runtime_event_map +from hercules.definitions.rest_action.runtime_flow_types.rest import Rest + + +REST_SETTING_ORDER = [ + "http_schema", + "http_url", + "http_method", + "http_auth", + "http_auth_value", + "input_schema", +] + + +class TestParameterDecorator: + def test_preserves_source_order(self): + @Parameter({"runtime_name": "first"}) + @Parameter({"runtime_name": "second"}) + @Parameter({"runtime_name": "third"}) + class Foo: + pass + + parameters = get_metadata("hercules:function_parameters", Foo) + assert [p.runtime_name for p in parameters] == ["first", "second", "third"] + + def test_works_for_single_parameter(self): + @Parameter({"runtime_name": "only"}) + class Bar: + pass + + parameters = get_metadata("hercules:function_parameters", Bar) + assert [p.runtime_name for p in parameters] == ["only"] + + +class TestEventMap: + def test_keeps_runtime_setting_order_regardless_of_override_order(self): + @Identifier("ScrambledOverrides") + @EventSetting({"identifier": "input_schema", "hidden": True, "default_value": {}}) + @EventSetting({"identifier": "http_auth_value", "hidden": True}) + @EventSetting({"identifier": "http_schema", "hidden": True, "default_value": "application/json"}) + @EventSetting({"identifier": "http_method", "hidden": True, "default_value": "POST"}) + class ScrambledOverrides(Rest): + pass + + definition = event_map(ScrambledOverrides) + assert [s.identifier for s in definition.settings] == REST_SETTING_ORDER + + def test_merges_override_properties(self): + @Identifier("MethodOverride") + @EventSetting({"identifier": "http_method", "hidden": True, "default_value": "POST"}) + class MethodOverride(Rest): + pass + + http_method = next( + s for s in event_map(MethodOverride).settings if s.identifier == "http_method" + ) + assert http_method.hidden is True + assert http_method.default_value == "POST" + assert http_method.name[0].content == "Method" + + def test_does_not_pollute_runtime_settings(self): + @Identifier("PollutionCheck") + @EventSetting({"identifier": "http_method", "hidden": True, "default_value": "POST"}) + class PollutionCheck(Rest): + pass + + event_map(PollutionCheck) + + rest_settings = runtime_event_map(Rest).settings or [] + assert [s.identifier for s in rest_settings] == REST_SETTING_ORDER + assert next(s for s in rest_settings if s.identifier == "http_method").hidden is None + + +class TestEventSettingDecorator: + def test_preserves_source_order(self): + @EventSetting({"identifier": "first"}) + @EventSetting({"identifier": "second"}) + @EventSetting({"identifier": "third"}) + class Event: + pass + + settings = get_metadata("hercules:flow_settings", Event) + assert [s.identifier for s in settings] == ["first", "second", "third"] From 68c02ee7b7d9f501aaef95f2af22144250b4e081 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 15:01:25 +0200 Subject: [PATCH 08/12] feat: update .gitignore to exclude generated definitions from code0-definition releases --- py/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/.gitignore b/py/.gitignore index 78c66d0..1bc62e3 100644 --- a/py/.gitignore +++ b/py/.gitignore @@ -14,3 +14,7 @@ venv/ # Node helper (hercules/_tsgen): keep sources + lockfile, ignore installed deps hercules/_tsgen/node_modules/ + +# Generated definitions (built from a code0-definition release via +# scripts/build_definitions.py); regenerated, not committed. +hercules/definitions/ From bdd8681c09b048c3fdfe2432c8a5ac9922e356a7 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 15:13:25 +0200 Subject: [PATCH 09/12] feat: configure editable install mode for setuptools compatibility with IDEs --- py/examples/simple-example-py/pyproject.toml | 6 ++++++ py/pyproject.toml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/py/examples/simple-example-py/pyproject.toml b/py/examples/simple-example-py/pyproject.toml index b5487d7..5f8109e 100644 --- a/py/examples/simple-example-py/pyproject.toml +++ b/py/examples/simple-example-py/pyproject.toml @@ -13,6 +13,12 @@ dependencies = [ [tool.uv.sources] code0-hercules = { path = "../..", editable = true } +# Build the editable install with setuptools' "compat" mode, which writes a plain +# directory `.pth` instead of a PEP 660 import-hook finder. IDEs (PyCharm/IntelliJ) +# resolve the `.pth` form but cannot follow the import-hook finder. +[tool.uv] +config-settings = { editable_mode = "compat" } + [dependency-groups] dev = [ "pytest>=7.4", diff --git a/py/pyproject.toml b/py/pyproject.toml index b03cc82..5dd8c5f 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -33,6 +33,12 @@ dev = [ "pytest-asyncio>=0.23", ] +# Build the editable install with setuptools' "compat" mode, which writes a plain +# directory `.pth` instead of a PEP 660 import-hook finder. IDEs (PyCharm/IntelliJ) +# resolve the `.pth` form but cannot follow the import-hook finder. +[tool.uv] +config-settings = { editable_mode = "compat" } + [project.scripts] hercules = "hercules.cli:main" From a65fa0219c0a9cbc34108f567a19a7877a81795a Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 15:20:02 +0200 Subject: [PATCH 10/12] feat: update .gitignore to exclude IDE configuration files --- py/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/.gitignore b/py/.gitignore index 1bc62e3..504934a 100644 --- a/py/.gitignore +++ b/py/.gitignore @@ -12,6 +12,10 @@ venv/ .pytest_cache/ .mypy_cache/ +# IDE (machine-specific: interpreter paths, module config) +.idea/ +*.iml + # Node helper (hercules/_tsgen): keep sources + lockfile, ignore installed deps hercules/_tsgen/node_modules/ From e321de52029df519c60619ba049700231de4c489 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 13 Aug 2026 16:52:33 +0200 Subject: [PATCH 11/12] feat: add CI configuration for Python SDK build and publish process --- .github/workflows/build.yml | 34 +++++++++++++++++++ .github/workflows/publish.yml | 45 ++++++++++++++++++++++++ py/README.md | 64 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 py/README.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3be243f..abe571f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,3 +26,37 @@ jobs: - name: Run tests run: npm run test + python: + runs-on: ubuntu-latest + + defaults: + run: + shell: bash + working-directory: py + + env: + # code0-definition release the built-in definitions are generated from. + DEFINITIONS_VERSION: ${{ vars.HERCULES_DEFINITIONS_VERSION || 'def-0.0.34' }} + + steps: + - uses: actions/checkout@v6 + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + python-version: '3.12' + - name: Set up Node + # Node powers the Pydantic <-> TypeScript schema conversion (hercules/_tsgen). + uses: actions/setup-node@v6 + with: + node-version: '25.9.x' + - name: Install dependencies + run: uv sync + - name: Install Node helper dependencies + run: npm ci --prefix hercules/_tsgen + - name: Generate definitions + run: uv run python scripts/build_definitions.py --version "$DEFINITIONS_VERSION" + - name: Run tests + run: uv run pytest + - name: Build package + run: uv build + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 539f200..87983d6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -36,3 +36,48 @@ jobs: run: npm run test - name: Publish package run: npm publish + + pypi: + runs-on: ubuntu-latest + + environment: packages + + permissions: + # Required for PyPI trusted publishing (OIDC); no API token needed. + id-token: write + + defaults: + run: + shell: bash + working-directory: py + + env: + # code0-definition release the built-in definitions are generated from. + DEFINITIONS_VERSION: ${{ vars.HERCULES_DEFINITIONS_VERSION || 'def-0.0.34' }} + + steps: + - uses: actions/checkout@v6 + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + python-version: '3.12' + - name: Set up Node + # Node powers the Pydantic <-> TypeScript schema conversion (hercules/_tsgen). + uses: actions/setup-node@v6 + with: + node-version: '25.9.x' + - name: Install dependencies + run: uv sync + - name: Install Node helper dependencies + run: npm ci --prefix hercules/_tsgen + - name: Set version + # The git tag (minus any leading "v") becomes the package version. + run: uv version "${GITHUB_REF_NAME#v}" + - name: Generate definitions + run: uv run python scripts/build_definitions.py --version "$DEFINITIONS_VERSION" + - name: Run tests + run: uv run pytest + - name: Build package + run: uv build + - name: Publish package + run: uv publish --trusted-publishing always diff --git a/py/README.md b/py/README.md new file mode 100644 index 0000000..83757d2 --- /dev/null +++ b/py/README.md @@ -0,0 +1,64 @@ +# code0-hercules (Python) + +Python SDK for the hercules action runner — the counterpart to the TypeScript +`@code0-tech/hercules` package. Define data types, functions, and events, and +connect them to aquila. + +## Install + +```bash +pip install code0-hercules +``` + +Requirements: + +- **Python 3.10+** +- **Node.js** — data-type schemas are converted to/from TypeScript via a bundled + Node helper (`hercules/_tsgen`). Its npm dependencies are installed automatically + on first use. + +## Develop + +This project uses [uv](https://docs.astral.sh/uv/). + +```bash +uv sync # create .venv + install (incl. dev deps) +npm ci --prefix hercules/_tsgen # install the Node schema helper +uv run pytest # run the tests +``` + +The runnable example lives in [`examples/simple-example-py`](examples/simple-example-py). + +## Built-in definitions + +The built-in data types / functions in `hercules/definitions/` are **generated** +(not committed) from a [`code0-definition`](https://github.com/code0-tech/code0-definition) +release: + +```bash +uv run python scripts/build_definitions.py --version def-0.0.34 +``` + +## Build & release + +Packages are built with `uv build` and published to PyPI from CI: + +- **`build.yml`** builds and tests the package on every push. +- **`publish.yml`** runs on a pushed git tag: it sets the package version from the + tag, regenerates the definitions, builds, and publishes to PyPI via + [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no token). + +To cut a release, push a PEP 440 version tag (e.g. `0.1.0`): + +```bash +git tag 0.1.0 && git push origin 0.1.0 +``` + +The `code0-definition` release used for the built-in definitions is controlled by +the `HERCULES_DEFINITIONS_VERSION` repository variable (default `def-0.0.34`). + +Build locally: + +```bash +uv build # -> dist/*.whl and dist/*.tar.gz +``` From 171cf3dcf3cb5b8560d1d1cdd04707876b95b54f Mon Sep 17 00:00:00 2001 From: Nico Sammito Date: Thu, 13 Aug 2026 21:55:34 +0200 Subject: [PATCH 12/12] Update py/pyproject.toml Co-authored-by: Niklas van Schrick Signed-off-by: Nico Sammito --- py/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/pyproject.toml b/py/pyproject.toml index 5dd8c5f..0039bec 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -8,7 +8,7 @@ version = "0.0.0" description = "Python SDK for the hercules action runner" readme = "README.md" requires-python = ">=3.10" -license = { text = "ISC" } +license = "MIT" dependencies = [ "grpcio>=1.64", "protobuf>=5.0",