From 5820bfd36f53a3e2725a46015e5cd79e6095bb6f Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sat, 12 Apr 2025 15:28:31 +0200 Subject: [PATCH 1/9] Added intial group support. --- .pre-commit-config.yaml | 3 +- poetry.lock | 488 +++++++++++++++-------------- taskiq_pipelines/__init__.py | 2 + taskiq_pipelines/constants.py | 1 + taskiq_pipelines/pipeliner.py | 27 ++ taskiq_pipelines/steps/__init__.py | 2 + taskiq_pipelines/steps/group.py | 115 +++++++ taskiq_pipelines/steps/mapper.py | 13 +- taskiq_pipelines/task_group.py | 86 +++++ 9 files changed, 499 insertions(+), 238 deletions(-) create mode 100644 taskiq_pipelines/steps/group.py create mode 100644 taskiq_pipelines/task_group.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f2cdc1..74167f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,8 @@ repos: args: - "check" - "--fix" - - "." + - "taskiq_pipelines" + - "tests" - id: mypy name: Validate types with MyPy diff --git a/poetry.lock b/poetry.lock index e21660d..8e6d819 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,14 +14,14 @@ files = [ [[package]] name = "anyio" -version = "4.8.0" +version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] @@ -31,8 +31,8 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -124,75 +124,75 @@ files = [ [[package]] name = "coverage" -version = "7.6.12" +version = "7.8.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, - {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, - {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, - {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, - {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, - {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, - {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, - {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, - {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, - {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, - {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, - {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, - {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, - {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, - {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, - {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, - {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, - {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, - {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, + {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, + {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, + {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, + {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, + {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, + {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, + {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, + {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, + {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, + {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, + {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, + {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, + {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, + {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, + {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, ] [package.dependencies] @@ -246,14 +246,14 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.17.0" +version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, - {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] [package.extras] @@ -263,14 +263,14 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "identify" -version = "2.6.8" +version = "2.6.9" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255"}, - {file = "identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc"}, + {file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, + {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, ] [package.extras] @@ -317,28 +317,31 @@ type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "izulu" -version = "0.5.4" +version = "0.50.0" description = "The exceptional library" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "izulu-0.5.4-py3-none-any.whl", hash = "sha256:6431499a04f68daca0b852dfa5cfbcb7be804166bcdc4efd4dd4e6dd7a3e5898"}, - {file = "izulu-0.5.4.tar.gz", hash = "sha256:a6619402ab3c04ca32bbfb5000138287691e0b47d9794ba55a10af403ed23644"}, + {file = "izulu-0.50.0-py3-none-any.whl", hash = "sha256:4e9ae2508844e7c5f62c468a8b9e2deba2f60325ef63f01e65b39fd9a6b3fab4"}, + {file = "izulu-0.50.0.tar.gz", hash = "sha256:cc8e252d5e8560c70b95380295008eeb0786f7b745a405a40d3556ab3252d5f5"}, ] +[package.extras] +compatibility = ["typing-extensions (>=4.5.0)"] + [[package]] name = "mypy" version = "1.15.0" @@ -443,20 +446,20 @@ files = [ [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -476,14 +479,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "4.1.0" +version = "4.2.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, - {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, + {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, + {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, ] [package.dependencies] @@ -531,20 +534,21 @@ files = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.3" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, + {file = "pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f"}, + {file = "pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" +pydantic-core = "2.33.1" typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -552,112 +556,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.1" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26"}, + {file = "pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde"}, + {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65"}, + {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc"}, + {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091"}, + {file = "pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383"}, + {file = "pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504"}, + {file = "pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24"}, + {file = "pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77"}, + {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961"}, + {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1"}, + {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c"}, + {file = "pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896"}, + {file = "pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83"}, + {file = "pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89"}, + {file = "pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8"}, + {file = "pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b"}, + {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39"}, + {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a"}, + {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db"}, + {file = "pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda"}, + {file = "pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4"}, + {file = "pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea"}, + {file = "pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a"}, + {file = "pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4"}, + {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde"}, + {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e"}, + {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd"}, + {file = "pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f"}, + {file = "pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40"}, + {file = "pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523"}, + {file = "pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d"}, + {file = "pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c"}, + {file = "pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18"}, + {file = "pydantic_core-2.33.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb"}, + {file = "pydantic_core-2.33.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6"}, + {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d"}, + {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96"}, + {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599"}, + {file = "pydantic_core-2.33.1-cp39-cp39-win32.whl", hash = "sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5"}, + {file = "pydantic_core-2.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3"}, + {file = "pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df"}, ] [package.dependencies] @@ -688,14 +691,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-cov" -version = "6.0.0" +version = "6.1.1" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, - {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, + {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, + {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, ] [package.dependencies] @@ -729,14 +732,14 @@ testing = ["filelock"] [[package]] name = "pytz" -version = "2025.1" +version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, - {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, ] [[package]] @@ -804,30 +807,30 @@ files = [ [[package]] name = "ruff" -version = "0.9.9" +version = "0.9.10" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367"}, - {file = "ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7"}, - {file = "ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e"}, - {file = "ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1"}, - {file = "ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1"}, - {file = "ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf"}, - {file = "ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933"}, + {file = "ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d"}, + {file = "ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d"}, + {file = "ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1"}, + {file = "ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c"}, + {file = "ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43"}, + {file = "ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c"}, + {file = "ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5"}, + {file = "ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8"}, + {file = "ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029"}, + {file = "ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1"}, + {file = "ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69"}, + {file = "ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7"}, ] [[package]] @@ -844,20 +847,20 @@ files = [ [[package]] name = "taskiq" -version = "0.11.13" +version = "0.11.16" description = "Distributed task queue with full async support" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "taskiq-0.11.13-py3-none-any.whl", hash = "sha256:df4db3c2d43b33360b1de18d9d7195397e553fa45b7a559be26236f08cecddad"}, - {file = "taskiq-0.11.13.tar.gz", hash = "sha256:6d6d14807921b8ffa95a8418f992cf30edb9d8f9557c5f737c82ecaab8c387ea"}, + {file = "taskiq-0.11.16-py3-none-any.whl", hash = "sha256:2cbc458890c0a5544a34ea2489bce255bcbc8459b2a344b62a43f7db51c1389c"}, + {file = "taskiq-0.11.16.tar.gz", hash = "sha256:52e73794503c41250e384f9e1a2645fa56fd25ffebbbf512ca708a7a46e728f3"}, ] [package.dependencies] anyio = ">=3" importlib-metadata = "*" -izulu = "0.5.4" +izulu = "0.50.0" packaging = ">=19" pycron = ">=3.0.0,<4.0.0" pydantic = ">=1.0,<=3.0" @@ -934,26 +937,41 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, + {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, ] +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "virtualenv" -version = "20.29.2" +version = "20.30.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, - {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, + {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"}, + {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"}, ] [package.dependencies] diff --git a/taskiq_pipelines/__init__.py b/taskiq_pipelines/__init__.py index 914aaa6..0cc7b6c 100644 --- a/taskiq_pipelines/__init__.py +++ b/taskiq_pipelines/__init__.py @@ -3,9 +3,11 @@ from taskiq_pipelines.exceptions import AbortPipeline, PipelineError from taskiq_pipelines.middleware import PipelineMiddleware from taskiq_pipelines.pipeliner import Pipeline +from taskiq_pipelines.task_group import Group __all__ = [ "AbortPipeline", + "Group", "Pipeline", "PipelineError", "PipelineMiddleware", diff --git a/taskiq_pipelines/constants.py b/taskiq_pipelines/constants.py index e3b4fa6..4ae1ea1 100644 --- a/taskiq_pipelines/constants.py +++ b/taskiq_pipelines/constants.py @@ -2,5 +2,6 @@ CURRENT_STEP = "_pipe_current_step" PIPELINE_DATA = "_pipe_data" +PARENT_TASK_ID = "_parent_task_id" EMPTY_PARAM_NAME: Literal[-1] = -1 diff --git a/taskiq_pipelines/pipeliner.py b/taskiq_pipelines/pipeliner.py index f7c3eb8..9f367a2 100644 --- a/taskiq_pipelines/pipeliner.py +++ b/taskiq_pipelines/pipeliner.py @@ -20,6 +20,8 @@ from taskiq_pipelines.constants import CURRENT_STEP, EMPTY_PARAM_NAME, PIPELINE_DATA from taskiq_pipelines.steps import FilterStep, MapperStep, SequentialStep, parse_step +from taskiq_pipelines.steps.group import GroupStep +from taskiq_pipelines.task_group import Group _ReturnType = TypeVar("_ReturnType") _FuncParams = ParamSpec("_FuncParams") @@ -325,6 +327,31 @@ def filter( ) return self + def group( + self: "Pipeline[_FuncParams, _ReturnType]", + group: Group[_T2], + ) -> "Pipeline[_FuncParams, _T2]": + """ + Add group task execution step. + + This step will run all tasks in parallel + and will wait for all of them to finish. + + Results of all tasks will be returned as an iterable + where each item is a result of the task in the group + with the same order. + + :param group: group to execute. + """ + self.steps.append( + DumpedStep( + step_type=GroupStep._step_name, + step_data=group.to_step().model_dump(), + task_id="", + ), + ) + return self # type: ignore + def dumpb(self) -> bytes: """ Dumps current pipeline as string. diff --git a/taskiq_pipelines/steps/__init__.py b/taskiq_pipelines/steps/__init__.py index 2b331a3..bb07146 100644 --- a/taskiq_pipelines/steps/__init__.py +++ b/taskiq_pipelines/steps/__init__.py @@ -5,6 +5,7 @@ from taskiq_pipelines.abc import AbstractStep from taskiq_pipelines.steps.filter import FilterStep +from taskiq_pipelines.steps.group import GroupStep from taskiq_pipelines.steps.mapper import MapperStep from taskiq_pipelines.steps.sequential import SequentialStep @@ -21,6 +22,7 @@ def parse_step(step_type: str, step_data: Dict[str, Any]) -> AbstractStep: __all__ = [ "FilterStep", + "GroupStep", "MapperStep", "SequentialStep", ] diff --git a/taskiq_pipelines/steps/group.py b/taskiq_pipelines/steps/group.py new file mode 100644 index 0000000..e51fd6a --- /dev/null +++ b/taskiq_pipelines/steps/group.py @@ -0,0 +1,115 @@ +from typing import Any, Dict, List, Optional + +import pydantic +from taskiq import ( + AsyncBroker, + Context, + TaskiqDepends, + TaskiqMessage, + TaskiqResult, + async_shared_broker, +) + +from taskiq_pipelines.abc import AbstractStep +from taskiq_pipelines.constants import CURRENT_STEP, PIPELINE_DATA +from taskiq_pipelines.steps.mapper import wait_tasks + + +@async_shared_broker.task(task_name="taskiq_pipelines.shared.wait_group_tasks") +async def wait_group_tasks( + task_ids: List[str], + check_interval: float, + skip_errors: bool = True, + context: Context = TaskiqDepends(), +) -> tuple[Any, ...]: + """Waits for subtasks to complete.""" + res = await wait_tasks( + task_ids, + check_interval=check_interval, + skip_errors=False, + none_if_errors=skip_errors, + context=context, + ) + return tuple(res) + + +class GroupStepItem(pydantic.BaseModel): + """Item of a group step.""" + + task_name: str + labels: Dict[str, Any] + labels_types: Optional[Dict[str, int]] = None + args: List[Any] + kwargs: Dict[str, Any] + + def from_message(self, message: TaskiqMessage) -> None: + """ + Parse labels and kwargs from message. + + :param message: message to parse. + """ + self.labels = message.labels + self.labels_types = message.labels_types + self.args = message.args + self.kwargs = message.kwargs + + def to_message(self, task_id: str) -> TaskiqMessage: + """ + Convert this item to message. + + :return: message + """ + return TaskiqMessage( + task_id=task_id, + task_name=self.task_name, + labels=self.labels, + labels_types=self.labels_types, + args=self.args, + kwargs=self.kwargs, + ) + + +class GroupStep(pydantic.BaseModel, AbstractStep, step_name="group"): + """Step that maps iterables.""" + + tasks: list[GroupStepItem] + skip_errors: bool + check_interval: float + + async def act( + self, + broker: AsyncBroker, + step_number: int, + parent_task_id: str, + task_id: str, + pipe_data: str, + result: "TaskiqResult[Any]", + ) -> None: + """ + Execute group action. + + This steps creates many small tasks + and one waiter task. + + The waiter task awaits for all small tasks to complete, + and then assembles the final result. + """ + ids: List[str] = [] + for task in self.tasks: + subtask_id = broker.id_generator() + ids.append(subtask_id) + await broker.kick(broker.formatter.dumps(task.to_message(subtask_id))) + + await ( + wait_group_tasks.kicker() + .with_broker(broker) + .with_task_id(task_id) + .with_labels( + **{CURRENT_STEP: step_number, PIPELINE_DATA: pipe_data}, # type: ignore + ) + .kiq( + task_ids=ids, + skip_errors=True, + check_interval=self.check_interval, + ) + ) diff --git a/taskiq_pipelines/steps/mapper.py b/taskiq_pipelines/steps/mapper.py index 25f4cb2..dab6152 100644 --- a/taskiq_pipelines/steps/mapper.py +++ b/taskiq_pipelines/steps/mapper.py @@ -1,4 +1,5 @@ import asyncio +from logging import getLogger from typing import Any, Dict, Iterable, List, Optional, Union import pydantic @@ -16,12 +17,15 @@ from taskiq_pipelines.constants import CURRENT_STEP, PIPELINE_DATA from taskiq_pipelines.exceptions import AbortPipeline, MappingError +logger = getLogger("taskiq_pipelines") + @async_shared_broker.task(task_name="taskiq_pipelines.shared.wait_tasks") -async def wait_tasks( +async def wait_tasks( # noqa: C901 task_ids: List[str], check_interval: float, skip_errors: bool = True, + none_if_errors: bool = False, context: Context = TaskiqDepends(), ) -> List[Any]: """ @@ -53,12 +57,16 @@ async def wait_tasks( if tasks_set: await asyncio.sleep(check_interval) - results = [] + results: List[Any] = [] for task_id in ordered_ids: result = await context.broker.result_backend.get_result(task_id) + logger.warning("Found error: %s", result.error) if result.is_err: if skip_errors: continue + if none_if_errors: + results.append(None) + continue err_cause = None if isinstance(result.error, BaseException): err_cause = result.error @@ -137,6 +145,7 @@ async def act( sub_task_ids, check_interval=self.check_interval, skip_errors=self.skip_errors, + none_if_errors=False, ) ) diff --git a/taskiq_pipelines/task_group.py b/taskiq_pipelines/task_group.py new file mode 100644 index 0000000..8e2d2b6 --- /dev/null +++ b/taskiq_pipelines/task_group.py @@ -0,0 +1,86 @@ +from types import CoroutineType +from typing import Any, Coroutine, Generic, Tuple, Union, overload + +from taskiq import AsyncTaskiqDecoratedTask +from taskiq.kicker import AsyncKicker +from typing_extensions import ParamSpec, TypeVar, TypeVarTuple, Unpack + +from taskiq_pipelines.steps.group import GroupStep, GroupStepItem + +_Tups = TypeVarTuple("_Tups") +_T = TypeVar("_T") +_TVal = TypeVar("_TVal") +_Params = ParamSpec("_Params") + + +class Group(Generic[_T]): + """ + Group of tasks. + + This class gathers multiple tasks together. + They will run in parallel + + :param skip_errors: If True, errors in one task will not affect others. + """ + + def __init__( + self: "Group[Tuple[()]]", + skip_errors: bool = False, + check_interval: float = 0.1, + ) -> None: + self.tasks: Tuple[GroupStepItem, ...] = () + self.skip_errors = skip_errors + self.check_interval = check_interval + + @overload + def add( + self: "Group[Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, Coroutine[Any, Any, _TVal]], + AsyncKicker[_Params, "CoroutineType[Any, Any, _TVal]"], + AsyncTaskiqDecoratedTask[_Params, Coroutine[Any, Any, _TVal]], + AsyncTaskiqDecoratedTask[_Params, "CoroutineType[Any, Any, _TVal]"], + ], + *args: _Params.args, + **kwargs: _Params.kwargs, + ) -> "Group[Tuple[Unpack[_Tups], _TVal]]": ... + + @overload + def add( + self: "Group[Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, _TVal], + AsyncTaskiqDecoratedTask[_Params, _TVal], + ], + *args: _Params.args, + **kwargs: _Params.kwargs, + ) -> "Group[Tuple[Unpack[_Tups], _TVal]]": ... + + def add( + self: "Group[Any]", + task: Union[AsyncKicker[_Params, Any], AsyncTaskiqDecoratedTask[_Params, Any]], + *args: _Params.args, + **kwargs: _Params.kwargs, + ) -> "Any": + """Add task to a group.""" + kicker = task.kicker() if isinstance(task, AsyncTaskiqDecoratedTask) else task + message = kicker._prepare_message(*args, **kwargs) + self.tasks = ( + *self.tasks, + GroupStepItem( + task_name=message.task_name, + labels=message.labels, + labels_types=message.labels_types, + args=message.args, + kwargs=message.kwargs, + ), + ) + return self + + def to_step(self) -> GroupStep: + """Convert group definition to a step.""" + return GroupStep( + tasks=list(self.tasks), + skip_errors=self.skip_errors, + check_interval=self.check_interval, + ) From 3fe9f81a991f9440bed8c677e71f5d13a5e181a3 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sat, 12 Apr 2025 23:45:26 +0200 Subject: [PATCH 2/9] Initial groups support. --- README.md | 11 ++++ taskiq_pipelines/abc.py | 6 +- taskiq_pipelines/pipeliner.py | 96 +++++++++++++++++++++++----- taskiq_pipelines/steps/filter.py | 8 +-- taskiq_pipelines/steps/group.py | 7 +- taskiq_pipelines/steps/mapper.py | 9 +-- taskiq_pipelines/steps/sequential.py | 15 ++--- 7 files changed, 114 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 7721808..5c1c23d 100644 --- a/README.md +++ b/README.md @@ -162,3 +162,14 @@ If called tasks returned `True` for some element, this element will be added in After the execution you'll get a list with filtered results. You can add filters by calling `.filter` method of the pipeline. + + +### Group steps + +This step groups together multiple tasks and sends them after the previous steps. + +To create a group you need to use `Group` class from `taskiq_pipelienes` like this: + +``` + +``` diff --git a/taskiq_pipelines/abc.py b/taskiq_pipelines/abc.py index 2259806..769eae5 100644 --- a/taskiq_pipelines/abc.py +++ b/taskiq_pipelines/abc.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Any, Dict, Type -from taskiq import AsyncBroker, TaskiqResult +from taskiq import AsyncBroker, AsyncTaskiqTask, TaskiqResult from typing_extensions import ClassVar @@ -26,9 +26,9 @@ async def act( step_number: int, parent_task_id: str, task_id: str, - pipe_data: str, + pipe_data: bytes, result: "TaskiqResult[Any]", - ) -> None: + ) -> AsyncTaskiqTask[Any]: """ Perform pipeline action. diff --git a/taskiq_pipelines/pipeliner.py b/taskiq_pipelines/pipeliner.py index 9f367a2..928d69a 100644 --- a/taskiq_pipelines/pipeliner.py +++ b/taskiq_pipelines/pipeliner.py @@ -13,7 +13,7 @@ ) import pydantic -from taskiq import AsyncBroker, AsyncTaskiqTask +from taskiq import AsyncBroker, AsyncTaskiqTask, TaskiqResult from taskiq.decor import AsyncTaskiqDecoratedTask from taskiq.kicker import AsyncKicker from typing_extensions import ParamSpec @@ -52,6 +52,25 @@ class Pipeline(Generic[_FuncParams, _ReturnType]): but it's nice to have. """ + @overload + def __init__( + self: "Pipeline[[], _ReturnType]", + broker: AsyncBroker, + task: Optional[Group[_ReturnType]] = None, + ) -> None: ... + + @overload + def __init__( + self, + broker: AsyncBroker, + task: Optional[ + Union[ + AsyncKicker[_FuncParams, _ReturnType], + AsyncTaskiqDecoratedTask[_FuncParams, _ReturnType], + ] + ] = None, + ) -> None: ... + def __init__( self, broker: AsyncBroker, @@ -59,13 +78,18 @@ def __init__( Union[ AsyncKicker[_FuncParams, _ReturnType], AsyncTaskiqDecoratedTask[_FuncParams, _ReturnType], + Group[_ReturnType], ] ] = None, ) -> None: self.broker = broker self.steps: "List[DumpedStep]" = [] - if task: - self.call_next(task) + if not task: + return + if isinstance(task, Group): + self.group(task) + return + self.call_next(task) @overload def call_next( @@ -379,6 +403,48 @@ def loadb(cls, broker: AsyncBroker, pipe_data: bytes) -> "Pipeline[Any, Any]": pipe.steps = DumpedSteps.model_validate(data) # type: ignore[assignment] return pipe + async def _kick_sequential( + self, + step: SequentialStep, + task_id: str, + *args: Any, + **kwargs: Any, + ) -> AsyncTaskiqTask[_ReturnType]: + kicker = ( + AsyncKicker( + step.task_name, + broker=self.broker, + labels=step.labels, + ) + .with_task_id(task_id) + .with_labels( + **{CURRENT_STEP: 0, PIPELINE_DATA: self.dumpb()}, # type: ignore + ) + ) + return await kicker.kiq(*args, **kwargs) + + async def _kick_group( + self, + group: GroupStep, + task_id: str, + ) -> AsyncTaskiqTask[Any]: + await group.act( + broker=self.broker, + task_id=task_id, + step_number=0, + parent_task_id="", + pipe_data=self.dumpb(), + result=TaskiqResult( + is_err=False, + return_value=None, + execution_time=0.0, + ), + ) + return AsyncTaskiqTask( + task_id=task_id, + result_backend=self.broker.result_backend, + ) + async def kiq( self, *args: _FuncParams.args, @@ -405,20 +471,18 @@ async def kiq( self._update_task_ids() step = self.steps[0] parsed_step = parse_step(step.step_type, step.step_data) - if not isinstance(parsed_step, SequentialStep): - raise ValueError("First step must be sequential.") - kicker = ( - AsyncKicker( - parsed_step.task_name, - broker=self.broker, - labels=parsed_step.labels, + if isinstance(parsed_step, SequentialStep): + taskiq_task = await self._kick_sequential( + parsed_step, + step.task_id, + *args, + **kwargs, ) - .with_task_id(step.task_id) - .with_labels( - **{CURRENT_STEP: 0, PIPELINE_DATA: self.dumpb()}, # type: ignore - ) - ) - taskiq_task = await kicker.kiq(*args, **kwargs) + elif isinstance(parsed_step, GroupStep): + taskiq_task = await self._kick_group(parsed_step, step.task_id) + else: + raise ValueError("First step must be sequential or a group.") + taskiq_task.task_id = self.steps[-1].task_id return taskiq_task diff --git a/taskiq_pipelines/steps/filter.py b/taskiq_pipelines/steps/filter.py index ac79a65..266c223 100644 --- a/taskiq_pipelines/steps/filter.py +++ b/taskiq_pipelines/steps/filter.py @@ -2,7 +2,7 @@ from typing import Any, Dict, Iterable, List, Optional, Union import pydantic -from taskiq import AsyncBroker, Context, TaskiqDepends, TaskiqResult +from taskiq import AsyncBroker, AsyncTaskiqTask, Context, TaskiqDepends, TaskiqResult from taskiq.brokers.shared_broker import async_shared_broker from taskiq.decor import AsyncTaskiqDecoratedTask from taskiq.kicker import AsyncKicker @@ -88,9 +88,9 @@ async def act( step_number: int, parent_task_id: str, task_id: str, - pipe_data: str, + pipe_data: bytes, result: "TaskiqResult[Any]", - ) -> None: + ) -> AsyncTaskiqTask[Any]: """ Run filter action. @@ -121,7 +121,7 @@ async def act( else: task = await kicker.kiq(item, **self.additional_kwargs) sub_task_ids.append(task.task_id) - await ( + return await ( filter_tasks.kicker() .with_task_id(task_id) .with_broker( diff --git a/taskiq_pipelines/steps/group.py b/taskiq_pipelines/steps/group.py index e51fd6a..23a5059 100644 --- a/taskiq_pipelines/steps/group.py +++ b/taskiq_pipelines/steps/group.py @@ -3,6 +3,7 @@ import pydantic from taskiq import ( AsyncBroker, + AsyncTaskiqTask, Context, TaskiqDepends, TaskiqMessage, @@ -82,9 +83,9 @@ async def act( step_number: int, parent_task_id: str, task_id: str, - pipe_data: str, + pipe_data: bytes, result: "TaskiqResult[Any]", - ) -> None: + ) -> AsyncTaskiqTask[Any]: """ Execute group action. @@ -100,7 +101,7 @@ async def act( ids.append(subtask_id) await broker.kick(broker.formatter.dumps(task.to_message(subtask_id))) - await ( + return await ( wait_group_tasks.kicker() .with_broker(broker) .with_task_id(task_id) diff --git a/taskiq_pipelines/steps/mapper.py b/taskiq_pipelines/steps/mapper.py index dab6152..9a3fad1 100644 --- a/taskiq_pipelines/steps/mapper.py +++ b/taskiq_pipelines/steps/mapper.py @@ -6,6 +6,7 @@ from taskiq import ( AsyncBroker, AsyncTaskiqDecoratedTask, + AsyncTaskiqTask, Context, TaskiqDepends, TaskiqResult, @@ -60,8 +61,8 @@ async def wait_tasks( # noqa: C901 results: List[Any] = [] for task_id in ordered_ids: result = await context.broker.result_backend.get_result(task_id) - logger.warning("Found error: %s", result.error) if result.is_err: + logger.warning("Found error: %s", result.error) if skip_errors: continue if none_if_errors: @@ -92,9 +93,9 @@ async def act( step_number: int, parent_task_id: str, task_id: str, - pipe_data: str, + pipe_data: bytes, result: "TaskiqResult[Any]", - ) -> None: + ) -> AsyncTaskiqTask[Any]: """ Runs mapping. @@ -132,7 +133,7 @@ async def act( task = await kicker.kiq(item, **self.additional_kwargs) sub_task_ids.append(task.task_id) - await ( + return await ( wait_tasks.kicker() .with_task_id(task_id) .with_broker( diff --git a/taskiq_pipelines/steps/sequential.py b/taskiq_pipelines/steps/sequential.py index 7b10fb0..e7c92e1 100644 --- a/taskiq_pipelines/steps/sequential.py +++ b/taskiq_pipelines/steps/sequential.py @@ -1,7 +1,7 @@ from typing import Any, Dict, Optional, Union import pydantic -from taskiq import AsyncBroker, AsyncTaskiqDecoratedTask, TaskiqResult +from taskiq import AsyncBroker, AsyncTaskiqDecoratedTask, AsyncTaskiqTask, TaskiqResult from taskiq.kicker import AsyncKicker from taskiq_pipelines.abc import AbstractStep @@ -30,9 +30,9 @@ async def act( step_number: int, parent_task_id: str, task_id: str, - pipe_data: str, + pipe_data: bytes, result: "TaskiqResult[Any]", - ) -> None: + ) -> AsyncTaskiqTask[Any]: """ Runs next task. @@ -64,11 +64,10 @@ async def act( ) if isinstance(self.param_name, str): self.additional_kwargs[self.param_name] = result.return_value - await kicker.kiq(**self.additional_kwargs) - elif self.param_name == EMPTY_PARAM_NAME: - await kicker.kiq(**self.additional_kwargs) - else: - await kicker.kiq(result.return_value, **self.additional_kwargs) + return await kicker.kiq(**self.additional_kwargs) + if self.param_name == EMPTY_PARAM_NAME: + return await kicker.kiq(**self.additional_kwargs) + return await kicker.kiq(result.return_value, **self.additional_kwargs) @classmethod def from_task( From c204f49c5316c50e194af45b6ef1e754955d81df Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sat, 12 Apr 2025 23:51:16 +0200 Subject: [PATCH 3/9] Added readme. --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c1c23d..f04995f 100644 --- a/README.md +++ b/README.md @@ -167,9 +167,56 @@ You can add filters by calling `.filter` method of the pipeline. ### Group steps This step groups together multiple tasks and sends them after the previous steps. +All tasks will be executed in parallel to each other and collected to a single tuple. To create a group you need to use `Group` class from `taskiq_pipelienes` like this: -``` +```python +import asyncio +from typing import Any + +from taskiq.brokers.inmemory_broker import InMemoryBroker + +from taskiq_pipelines import Group, Pipeline, PipelineMiddleware + +broker = InMemoryBroker() +broker.add_middlewares(PipelineMiddleware()) + + +@broker.task +def add_one(value: int) -> int: + return value + 1 + + +@broker.task +def mul_two(val: int) -> int: + return val * 2 + + +@broker.task +def to_string(val: Any) -> str: + return str(val) + + +async def main(): + pipe = ( + Pipeline(broker).group( + Group() + # Here we start task that adds 1 to 1 + .add(add_one, 1) + # Here's a task that multiplies 2 by 2 + .add(mul_two, 2) + # Here we map all results to string + ).map(to_string) + ) + task = await pipe.kiq() + result = await task.wait_result() + # Here it should output + # Calculated value: ['2', '4'] + print("Calculated value:", result.return_value) + + +if __name__ == "__main__": + asyncio.run(main()) ``` From 18ddcab54318889d819a22c21592897459b06428 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sun, 13 Apr 2025 00:07:23 +0200 Subject: [PATCH 4/9] Fixed types for skip_errors. --- README.md | 11 +++++-- taskiq_pipelines/pipeliner.py | 6 ++-- taskiq_pipelines/task_group.py | 56 +++++++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f04995f..bd6ae3d 100644 --- a/README.md +++ b/README.md @@ -200,8 +200,15 @@ def to_string(val: Any) -> str: async def main(): pipe = ( - Pipeline(broker).group( - Group() + Pipeline(broker) + .group( + Group( + # Aborts pipeline + # if any of tasks fails + skip_errors=False, + # How often to check for completion. + check_interval=0.1, + ) # Here we start task that adds 1 to 1 .add(add_one, 1) # Here's a task that multiplies 2 by 2 diff --git a/taskiq_pipelines/pipeliner.py b/taskiq_pipelines/pipeliner.py index 928d69a..dbd21da 100644 --- a/taskiq_pipelines/pipeliner.py +++ b/taskiq_pipelines/pipeliner.py @@ -56,7 +56,7 @@ class Pipeline(Generic[_FuncParams, _ReturnType]): def __init__( self: "Pipeline[[], _ReturnType]", broker: AsyncBroker, - task: Optional[Group[_ReturnType]] = None, + task: Optional[Group[Any, _ReturnType]] = None, ) -> None: ... @overload @@ -78,7 +78,7 @@ def __init__( Union[ AsyncKicker[_FuncParams, _ReturnType], AsyncTaskiqDecoratedTask[_FuncParams, _ReturnType], - Group[_ReturnType], + Group[Any, _ReturnType], ] ] = None, ) -> None: @@ -353,7 +353,7 @@ def filter( def group( self: "Pipeline[_FuncParams, _ReturnType]", - group: Group[_T2], + group: Group[Any, _T2], ) -> "Pipeline[_FuncParams, _T2]": """ Add group task execution step. diff --git a/taskiq_pipelines/task_group.py b/taskiq_pipelines/task_group.py index 8e2d2b6..8ae02d9 100644 --- a/taskiq_pipelines/task_group.py +++ b/taskiq_pipelines/task_group.py @@ -1,5 +1,5 @@ from types import CoroutineType -from typing import Any, Coroutine, Generic, Tuple, Union, overload +from typing import Any, Coroutine, Generic, Literal, Optional, Tuple, Union, overload from taskiq import AsyncTaskiqDecoratedTask from taskiq.kicker import AsyncKicker @@ -9,11 +9,13 @@ _Tups = TypeVarTuple("_Tups") _T = TypeVar("_T") +# Whether skip_errors is set to True or False +_S = TypeVar("_S", bound=bool) _TVal = TypeVar("_TVal") _Params = ParamSpec("_Params") -class Group(Generic[_T]): +class Group(Generic[_S, _T]): """ Group of tasks. @@ -23,8 +25,22 @@ class Group(Generic[_T]): :param skip_errors: If True, errors in one task will not affect others. """ + @overload + def __init__( + self: "Group[Literal[True], Tuple[()]]", + skip_errors: Literal[True], + check_interval: float = 0.1, + ) -> None: ... + + @overload def __init__( - self: "Group[Tuple[()]]", + self: "Group[Literal[False], Tuple[()]]", + skip_errors: bool = False, + check_interval: float = 0.1, + ) -> None: ... + + def __init__( + self: "Group[Any, Tuple[()]]", skip_errors: bool = False, check_interval: float = 0.1, ) -> None: @@ -34,7 +50,7 @@ def __init__( @overload def add( - self: "Group[Tuple[Unpack[_Tups]]]", + self: "Group[Literal[True], Tuple[Unpack[_Tups]]]", task: Union[ AsyncKicker[_Params, Coroutine[Any, Any, _TVal]], AsyncKicker[_Params, "CoroutineType[Any, Any, _TVal]"], @@ -43,21 +59,45 @@ def add( ], *args: _Params.args, **kwargs: _Params.kwargs, - ) -> "Group[Tuple[Unpack[_Tups], _TVal]]": ... + ) -> "Group[_S, Tuple[Unpack[_Tups], Optional[_TVal]]]": ... + + @overload + def add( + self: "Group[Literal[False], Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, Coroutine[Any, Any, _TVal]], + AsyncKicker[_Params, "CoroutineType[Any, Any, _TVal]"], + AsyncTaskiqDecoratedTask[_Params, Coroutine[Any, Any, _TVal]], + AsyncTaskiqDecoratedTask[_Params, "CoroutineType[Any, Any, _TVal]"], + ], + *args: _Params.args, + **kwargs: _Params.kwargs, + ) -> "Group[_S, Tuple[Unpack[_Tups], _TVal]]": ... + + @overload + def add( + self: "Group[Literal[True], Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, _TVal], + AsyncTaskiqDecoratedTask[_Params, _TVal], + ], + *args: _Params.args, + **kwargs: _Params.kwargs, + ) -> "Group[_S, Tuple[Unpack[_Tups], Optional[_TVal]]]": ... @overload def add( - self: "Group[Tuple[Unpack[_Tups]]]", + self: "Group[Literal[False], Tuple[Unpack[_Tups]]]", task: Union[ AsyncKicker[_Params, _TVal], AsyncTaskiqDecoratedTask[_Params, _TVal], ], *args: _Params.args, **kwargs: _Params.kwargs, - ) -> "Group[Tuple[Unpack[_Tups], _TVal]]": ... + ) -> "Group[_S, Tuple[Unpack[_Tups], _TVal]]": ... def add( - self: "Group[Any]", + self: "Group[Any, Any]", task: Union[AsyncKicker[_Params, Any], AsyncTaskiqDecoratedTask[_Params, Any]], *args: _Params.args, **kwargs: _Params.kwargs, From d979c437a5c0cac58c61b27acadcee356e328aab Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sun, 13 Apr 2025 00:10:05 +0200 Subject: [PATCH 5/9] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd6ae3d..424e9c9 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ You can add filters by calling `.filter` method of the pipeline. This step groups together multiple tasks and sends them after the previous steps. All tasks will be executed in parallel to each other and collected to a single tuple. -To create a group you need to use `Group` class from `taskiq_pipelienes` like this: +To create a group you need to use `Group` class from `taskiq_pipelines` like this: ```python import asyncio From b89b8bc7723f574475daf1c2521a0a4fad6a5f84 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sun, 13 Apr 2025 16:32:19 +0200 Subject: [PATCH 6/9] Added group with args. --- taskiq_pipelines/pipeliner.py | 20 ++++-- taskiq_pipelines/steps/group.py | 28 ++++++-- taskiq_pipelines/task_group.py | 120 ++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 8 deletions(-) diff --git a/taskiq_pipelines/pipeliner.py b/taskiq_pipelines/pipeliner.py index dbd21da..a2e30dd 100644 --- a/taskiq_pipelines/pipeliner.py +++ b/taskiq_pipelines/pipeliner.py @@ -21,7 +21,7 @@ from taskiq_pipelines.constants import CURRENT_STEP, EMPTY_PARAM_NAME, PIPELINE_DATA from taskiq_pipelines.steps import FilterStep, MapperStep, SequentialStep, parse_step from taskiq_pipelines.steps.group import GroupStep -from taskiq_pipelines.task_group import Group +from taskiq_pipelines.task_group import Group, GroupWithArgs _ReturnType = TypeVar("_ReturnType") _FuncParams = ParamSpec("_FuncParams") @@ -54,9 +54,9 @@ class Pipeline(Generic[_FuncParams, _ReturnType]): @overload def __init__( - self: "Pipeline[[], _ReturnType]", + self: "Pipeline[[], Any]", broker: AsyncBroker, - task: Optional[Group[Any, _ReturnType]] = None, + task: None = None, ) -> None: ... @overload @@ -351,10 +351,22 @@ def filter( ) return self + @overload + def group( + self: "Pipeline[_FuncParams, _ReturnType]", + group: GroupWithArgs[Any, _T2], + ) -> "Pipeline[_FuncParams, _T2]": ... + + @overload def group( self: "Pipeline[_FuncParams, _ReturnType]", group: Group[Any, _T2], - ) -> "Pipeline[_FuncParams, _T2]": + ) -> "Pipeline[_FuncParams, _T2]": ... + + def group( + self: "Pipeline[_FuncParams, _ReturnType]", + group: Group[Any, Any] | GroupWithArgs[Any, Any], + ) -> "Pipeline[_FuncParams, Any]": """ Add group task execution step. diff --git a/taskiq_pipelines/steps/group.py b/taskiq_pipelines/steps/group.py index 23a5059..9936f29 100644 --- a/taskiq_pipelines/steps/group.py +++ b/taskiq_pipelines/steps/group.py @@ -41,6 +41,7 @@ class GroupStepItem(pydantic.BaseModel): labels: Dict[str, Any] labels_types: Optional[Dict[str, int]] = None args: List[Any] + param_name: Optional[str] kwargs: Dict[str, Any] def from_message(self, message: TaskiqMessage) -> None: @@ -54,19 +55,31 @@ def from_message(self, message: TaskiqMessage) -> None: self.args = message.args self.kwargs = message.kwargs - def to_message(self, task_id: str) -> TaskiqMessage: + def to_message( + self, + task_id: str, + result: Optional[TaskiqResult[Any]] = None, + ) -> TaskiqMessage: """ Convert this item to message. :return: message """ + args = self.args + kwargs = self.kwargs + if result: + if self.param_name: + kwargs[self.param_name] = result.return_value + else: + args = [result.return_value, *args] + return TaskiqMessage( task_id=task_id, task_name=self.task_name, labels=self.labels, labels_types=self.labels_types, - args=self.args, - kwargs=self.kwargs, + args=args, + kwargs=kwargs, ) @@ -76,6 +89,7 @@ class GroupStep(pydantic.BaseModel, AbstractStep, step_name="group"): tasks: list[GroupStepItem] skip_errors: bool check_interval: float + pass_args: bool = False async def act( self, @@ -99,7 +113,13 @@ async def act( for task in self.tasks: subtask_id = broker.id_generator() ids.append(subtask_id) - await broker.kick(broker.formatter.dumps(task.to_message(subtask_id))) + + if self.pass_args: + message = task.to_message(subtask_id, result) + else: + message = task.to_message(subtask_id, None) + + await broker.kick(broker.formatter.dumps(message)) return await ( wait_group_tasks.kicker() diff --git a/taskiq_pipelines/task_group.py b/taskiq_pipelines/task_group.py index 8ae02d9..81177ce 100644 --- a/taskiq_pipelines/task_group.py +++ b/taskiq_pipelines/task_group.py @@ -113,6 +113,7 @@ def add( labels_types=message.labels_types, args=message.args, kwargs=message.kwargs, + param_name=None, ), ) return self @@ -123,4 +124,123 @@ def to_step(self) -> GroupStep: tasks=list(self.tasks), skip_errors=self.skip_errors, check_interval=self.check_interval, + pass_args=False, + ) + + +class GroupWithArgs(Generic[_S, _T]): + """ + Group of tasks with args. + + This class gathers multiple tasks together. + They will run in parallel. The only difference + with Group is that it passes the result + of the previous task as the first argument + to all of the tasks in the group. + + :param skip_errors: If True, errors in one task will not affect others. + """ + + # If skip_errors is set to True, + # We update first generic type to Literal[True]. + @overload + def __init__( + self: "GroupWithArgs[Literal[True], Tuple[()]]", + skip_errors: Literal[True], + check_interval: float = 0.1, + ) -> None: ... + + @overload + def __init__( + self: "GroupWithArgs[Literal[False], Tuple[()]]", + skip_errors: bool = False, + check_interval: float = 0.1, + ) -> None: ... + + def __init__( + self: "GroupWithArgs[Any, Tuple[()]]", + skip_errors: bool = False, + check_interval: float = 0.1, + ) -> None: + self.tasks: Tuple[GroupStepItem, ...] = () + self.skip_errors = skip_errors + self.check_interval = check_interval + + @overload + def add( + self: "GroupWithArgs[Literal[True], Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, Coroutine[Any, Any, _TVal]], + AsyncKicker[_Params, "CoroutineType[Any, Any, _TVal]"], + AsyncTaskiqDecoratedTask[_Params, Coroutine[Any, Any, _TVal]], + AsyncTaskiqDecoratedTask[_Params, "CoroutineType[Any, Any, _TVal]"], + ], + param_name: Optional[str] = None, + **additional_kwargs: Any, + ) -> "GroupWithArgs[_S, Tuple[Unpack[_Tups], Optional[_TVal]]]": ... + + @overload + def add( + self: "GroupWithArgs[Literal[False], Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, Coroutine[Any, Any, _TVal]], + AsyncKicker[_Params, "CoroutineType[Any, Any, _TVal]"], + AsyncTaskiqDecoratedTask[_Params, Coroutine[Any, Any, _TVal]], + AsyncTaskiqDecoratedTask[_Params, "CoroutineType[Any, Any, _TVal]"], + ], + param_name: Optional[str] = None, + **additional_kwargs: Any, + ) -> "GroupWithArgs[_S, Tuple[Unpack[_Tups], _TVal]]": ... + + @overload + def add( + self: "GroupWithArgs[Literal[True], Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, _TVal], + AsyncTaskiqDecoratedTask[_Params, _TVal], + ], + param_name: Optional[str] = None, + **additional_kwargs: Any, + ) -> "GroupWithArgs[_S, Tuple[Unpack[_Tups], Optional[_TVal]]]": ... + + @overload + def add( + self: "GroupWithArgs[Literal[False], Tuple[Unpack[_Tups]]]", + task: Union[ + AsyncKicker[_Params, _TVal], + AsyncTaskiqDecoratedTask[_Params, _TVal], + ], + param_name: Optional[str] = None, + **additional_kwargs: Any, + ) -> "GroupWithArgs[_S, Tuple[Unpack[_Tups], _TVal]]": ... + + def add( + self: "GroupWithArgs[Any, Any]", + task: Union[AsyncKicker[_Params, Any], AsyncTaskiqDecoratedTask[_Params, Any]], + param_name: Optional[str] = None, + **additional_kwargs: Any, + ) -> "Any": + """Add task to a group.""" + kicker = task.kicker() if isinstance(task, AsyncTaskiqDecoratedTask) else task + message = kicker._prepare_message(**additional_kwargs) + self.tasks = ( + *self.tasks, + GroupStepItem( + task_name=message.task_name, + labels=message.labels, + labels_types=message.labels_types, + args=message.args, + kwargs=message.kwargs, + param_name=param_name, + ), + ) + return self + + def to_step(self) -> GroupStep: + """Convert group definition to a step.""" + return GroupStep( + tasks=list(self.tasks), + skip_errors=self.skip_errors, + check_interval=self.check_interval, + pass_args=True, ) From 5a941fd71fa08e669508001b6965a825cac3bcf3 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sun, 13 Apr 2025 16:41:15 +0200 Subject: [PATCH 7/9] Fixed a bug, added docs. --- README.md | 63 +++++++++++++++++++++++++++++++++ taskiq_pipelines/__init__.py | 3 +- taskiq_pipelines/steps/group.py | 4 +-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 424e9c9..b762a17 100644 --- a/README.md +++ b/README.md @@ -227,3 +227,66 @@ if __name__ == "__main__": asyncio.run(main()) ``` + +Aletrnatively, you can use `GroupWithArgs` class to pass arguments of previous step +as an argument to called functions. Here's an example: + +```python +import asyncio +from typing import Any + +from taskiq.brokers.inmemory_broker import InMemoryBroker + +from taskiq_pipelines import GroupWithArgs, Pipeline, PipelineMiddleware + +broker = InMemoryBroker() +broker.add_middlewares(PipelineMiddleware()) + + +@broker.task +def add_one(value: int) -> int: + return value + 1 + + +@broker.task +def mul_two(val: int) -> int: + return val * 2 + + +@broker.task +def to_string(val: Any) -> str: + return str(val) + + +async def main(): + pipe = ( + # The pipelines starts with `add_one` task. + Pipeline(broker, add_one) + # All values are passed to the group + .group( + GroupWithArgs( + # Aborts pipeline + # if any of tasks fails + skip_errors=False, + # How often to check for completion. + check_interval=0.1, + ) + # Here we start task that adds 1 to result + # of the previous task. + # The result is passed as keyword argument "value" + .add(add_one, param_name="value") + # Here's a task that multiplies 2 + .add(mul_two) + # Here we map all results to string + ).map(to_string) + ) + task = await pipe.kiq(1) + result = await task.wait_result() + # Here it should output + # Calculated value: ['3', '4'] + print("Calculated value:", result.return_value) + + +if __name__ == "__main__": + asyncio.run(main()) +``` diff --git a/taskiq_pipelines/__init__.py b/taskiq_pipelines/__init__.py index 0cc7b6c..691c583 100644 --- a/taskiq_pipelines/__init__.py +++ b/taskiq_pipelines/__init__.py @@ -3,11 +3,12 @@ from taskiq_pipelines.exceptions import AbortPipeline, PipelineError from taskiq_pipelines.middleware import PipelineMiddleware from taskiq_pipelines.pipeliner import Pipeline -from taskiq_pipelines.task_group import Group +from taskiq_pipelines.task_group import Group, GroupWithArgs __all__ = [ "AbortPipeline", "Group", + "GroupWithArgs", "Pipeline", "PipelineError", "PipelineMiddleware", diff --git a/taskiq_pipelines/steps/group.py b/taskiq_pipelines/steps/group.py index 9936f29..bbafcf4 100644 --- a/taskiq_pipelines/steps/group.py +++ b/taskiq_pipelines/steps/group.py @@ -20,7 +20,7 @@ async def wait_group_tasks( task_ids: List[str], check_interval: float, - skip_errors: bool = True, + skip_errors: bool = False, context: Context = TaskiqDepends(), ) -> tuple[Any, ...]: """Waits for subtasks to complete.""" @@ -130,7 +130,7 @@ async def act( ) .kiq( task_ids=ids, - skip_errors=True, + skip_errors=self.skip_errors, check_interval=self.check_interval, ) ) From ba0648519870d5fbdcbaaa436e70d395334b5d79 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sun, 13 Apr 2025 16:48:01 +0200 Subject: [PATCH 8/9] Fixed older python support. --- taskiq_pipelines/pipeliner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskiq_pipelines/pipeliner.py b/taskiq_pipelines/pipeliner.py index a2e30dd..aee6ca9 100644 --- a/taskiq_pipelines/pipeliner.py +++ b/taskiq_pipelines/pipeliner.py @@ -365,7 +365,7 @@ def group( def group( self: "Pipeline[_FuncParams, _ReturnType]", - group: Group[Any, Any] | GroupWithArgs[Any, Any], + group: Union[Group[Any, Any], GroupWithArgs[Any, Any]], ) -> "Pipeline[_FuncParams, Any]": """ Add group task execution step. From a0147a595c4c045fc9ddadb6d1ead164c3cb6a4f Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Sun, 13 Apr 2025 16:51:49 +0200 Subject: [PATCH 9/9] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b762a17..ee2c75b 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ if __name__ == "__main__": ``` -Aletrnatively, you can use `GroupWithArgs` class to pass arguments of previous step +Alternatively, you can use `GroupWithArgs` class to pass arguments of previous step as an argument to called functions. Here's an example: ```python