From 68e76afc372562b680a8dc7b7a38b16322c2ae8d Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 12 Aug 2025 22:03:21 +0800 Subject: [PATCH 01/20] feat(sdk/python): Update dstack python SDK. --- sdk/python/pdm.lock | 2194 ++++++++++------- sdk/python/pyproject.toml | 112 +- sdk/python/src/dstack_sdk/__init__.py | 43 +- sdk/python/src/dstack_sdk/dstack_client.py | 508 ++-- sdk/python/src/dstack_sdk/encrypt_env_vars.py | 101 + sdk/python/src/dstack_sdk/ethereum.py | 58 +- sdk/python/src/dstack_sdk/get_compose_hash.py | 240 ++ sdk/python/src/dstack_sdk/solana.py | 56 +- .../verify_env_encrypt_public_key.py | 119 + sdk/python/tests/test_client.py | 207 +- sdk/python/tests/test_encrypt_env_vars.py | 110 + sdk/python/tests/test_ethereum.py | 67 +- sdk/python/tests/test_get_compose_hash.py | 242 ++ sdk/python/tests/test_solana.py | 65 +- .../test_verify_env_encrypt_public_key.py | 168 ++ 15 files changed, 3282 insertions(+), 1008 deletions(-) create mode 100644 sdk/python/src/dstack_sdk/encrypt_env_vars.py create mode 100644 sdk/python/src/dstack_sdk/get_compose_hash.py create mode 100644 sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py create mode 100644 sdk/python/tests/test_encrypt_env_vars.py create mode 100644 sdk/python/tests/test_get_compose_hash.py create mode 100644 sdk/python/tests/test_verify_env_encrypt_public_key.py diff --git a/sdk/python/pdm.lock b/sdk/python/pdm.lock index 7ae53fed5..58df8a8b7 100644 --- a/sdk/python/pdm.lock +++ b/sdk/python/pdm.lock @@ -2,34 +2,34 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "ethereum", "solana", "test"] +groups = ["default", "ethereum", "lint", "solana", "test"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:751bc2b4d3623179594270fbaf1d5f379212384d7a462ccfed08e554e44ef9e7" +content_hash = "sha256:66159ee78fa4864888c47ee0dc6be4d633f01f108bdb1b6f73abfc1a38c870f1" [[metadata.targets]] requires_python = ">=3.10" [[package]] name = "aiohappyeyeballs" -version = "2.5.0" +version = "2.6.1" requires_python = ">=3.9" summary = "Happy Eyeballs for asyncio" groups = ["ethereum"] files = [ - {file = "aiohappyeyeballs-2.5.0-py3-none-any.whl", hash = "sha256:0850b580748c7071db98bffff6d4c94028d0d3035acc20fd721a0ce7e8cac35d"}, - {file = "aiohappyeyeballs-2.5.0.tar.gz", hash = "sha256:18fde6204a76deeabc97c48bdd01d5801cfda5d6b9c8bbeb1aaaee9d648ca191"}, + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, ] [[package]] name = "aiohttp" -version = "3.11.13" +version = "3.12.15" requires_python = ">=3.9" summary = "Async http client/server framework (asyncio)" groups = ["ethereum"] dependencies = [ - "aiohappyeyeballs>=2.3.0", - "aiosignal>=1.1.2", + "aiohappyeyeballs>=2.5.0", + "aiosignal>=1.4.0", "async-timeout<6.0,>=4.0; python_version < \"3.11\"", "attrs>=17.3.0", "frozenlist>=1.1.1", @@ -38,85 +38,90 @@ dependencies = [ "yarl<2.0,>=1.17.0", ] files = [ - {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4fe27dbbeec445e6e1291e61d61eb212ee9fed6e47998b27de71d70d3e8777d"}, - {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e64ca2dbea28807f8484c13f684a2f761e69ba2640ec49dacd342763cc265ef"}, - {file = "aiohttp-3.11.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9840be675de208d1f68f84d578eaa4d1a36eee70b16ae31ab933520c49ba1325"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28a772757c9067e2aee8a6b2b425d0efaa628c264d6416d283694c3d86da7689"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b88aca5adbf4625e11118df45acac29616b425833c3be7a05ef63a6a4017bfdb"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce10ddfbe26ed5856d6902162f71b8fe08545380570a885b4ab56aecfdcb07f4"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa48dac27f41b36735c807d1ab093a8386701bbf00eb6b89a0f69d9fa26b3671"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89ce611b1eac93ce2ade68f1470889e0173d606de20c85a012bfa24be96cf867"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78e4dd9c34ec7b8b121854eb5342bac8b02aa03075ae8618b6210a06bbb8a115"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:66047eacbc73e6fe2462b77ce39fc170ab51235caf331e735eae91c95e6a11e4"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ad8f1c19fe277eeb8bc45741c6d60ddd11d705c12a4d8ee17546acff98e0802"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64815c6f02e8506b10113ddbc6b196f58dbef135751cc7c32136df27b736db09"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:967b93f21b426f23ca37329230d5bd122f25516ae2f24a9cea95a30023ff8283"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf1f31f83d16ec344136359001c5e871915c6ab685a3d8dee38e2961b4c81730"}, - {file = "aiohttp-3.11.13-cp310-cp310-win32.whl", hash = "sha256:00c8ac69e259c60976aa2edae3f13d9991cf079aaa4d3cd5a49168ae3748dee3"}, - {file = "aiohttp-3.11.13-cp310-cp310-win_amd64.whl", hash = "sha256:90d571c98d19a8b6e793b34aa4df4cee1e8fe2862d65cc49185a3a3d0a1a3996"}, - {file = "aiohttp-3.11.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b35aab22419ba45f8fc290d0010898de7a6ad131e468ffa3922b1b0b24e9d2e"}, - {file = "aiohttp-3.11.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81cba651db8795f688c589dd11a4fbb834f2e59bbf9bb50908be36e416dc760"}, - {file = "aiohttp-3.11.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f55d0f242c2d1fcdf802c8fabcff25a9d85550a4cf3a9cf5f2a6b5742c992839"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4bea08a6aad9195ac9b1be6b0c7e8a702a9cec57ce6b713698b4a5afa9c2e33"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6070bcf2173a7146bb9e4735b3c62b2accba459a6eae44deea0eb23e0035a23"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:718d5deb678bc4b9d575bfe83a59270861417da071ab44542d0fcb6faa686636"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f6b2c5b4a4d22b8fb2c92ac98e0747f5f195e8e9448bfb7404cd77e7bfa243f"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747ec46290107a490d21fe1ff4183bef8022b848cf9516970cb31de6d9460088"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01816f07c9cc9d80f858615b1365f8319d6a5fd079cd668cc58e15aafbc76a54"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a08ad95fcbd595803e0c4280671d808eb170a64ca3f2980dd38e7a72ed8d1fea"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c97be90d70f7db3aa041d720bfb95f4869d6063fcdf2bb8333764d97e319b7d0"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ab915a57c65f7a29353c8014ac4be685c8e4a19e792a79fe133a8e101111438e"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:35cda4e07f5e058a723436c4d2b7ba2124ab4e0aa49e6325aed5896507a8a42e"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:af55314407714fe77a68a9ccaab90fdb5deb57342585fd4a3a8102b6d4370080"}, - {file = "aiohttp-3.11.13-cp311-cp311-win32.whl", hash = "sha256:42d689a5c0a0c357018993e471893e939f555e302313d5c61dfc566c2cad6185"}, - {file = "aiohttp-3.11.13-cp311-cp311-win_amd64.whl", hash = "sha256:b73a2b139782a07658fbf170fe4bcdf70fc597fae5ffe75e5b67674c27434a9f"}, - {file = "aiohttp-3.11.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90"}, - {file = "aiohttp-3.11.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d"}, - {file = "aiohttp-3.11.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637"}, - {file = "aiohttp-3.11.13-cp312-cp312-win32.whl", hash = "sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee"}, - {file = "aiohttp-3.11.13-cp312-cp312-win_amd64.whl", hash = "sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8"}, - {file = "aiohttp-3.11.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9862d077b9ffa015dbe3ce6c081bdf35135948cb89116e26667dd183550833d1"}, - {file = "aiohttp-3.11.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbfef0666ae9e07abfa2c54c212ac18a1f63e13e0760a769f70b5717742f3ece"}, - {file = "aiohttp-3.11.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a1f7d857c4fcf7cabb1178058182c789b30d85de379e04f64c15b7e88d66fb"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba40b7ae0f81c7029583a338853f6607b6d83a341a3dcde8bed1ea58a3af1df9"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5b95787335c483cd5f29577f42bbe027a412c5431f2f80a749c80d040f7ca9f"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7d474c5c1f0b9405c1565fafdc4429fa7d986ccbec7ce55bc6a330f36409cad"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e83fb1991e9d8982b3b36aea1e7ad27ea0ce18c14d054c7a404d68b0319eebb"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4586a68730bd2f2b04a83e83f79d271d8ed13763f64b75920f18a3a677b9a7f0"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fe4eb0e7f50cdb99b26250d9328faef30b1175a5dbcfd6d0578d18456bac567"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2a8a6bc19818ac3e5596310ace5aa50d918e1ebdcc204dc96e2f4d505d51740c"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f27eec42f6c3c1df09cfc1f6786308f8b525b8efaaf6d6bd76c1f52c6511f6a"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2a4a13dfbb23977a51853b419141cd0a9b9573ab8d3a1455c6e63561387b52ff"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:02876bf2f69b062584965507b07bc06903c2dc93c57a554b64e012d636952654"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b992778d95b60a21c4d8d4a5f15aaab2bd3c3e16466a72d7f9bfd86e8cea0d4b"}, - {file = "aiohttp-3.11.13-cp313-cp313-win32.whl", hash = "sha256:507ab05d90586dacb4f26a001c3abf912eb719d05635cbfad930bdbeb469b36c"}, - {file = "aiohttp-3.11.13-cp313-cp313-win_amd64.whl", hash = "sha256:5ceb81a4db2decdfa087381b5fc5847aa448244f973e5da232610304e199e7b2"}, - {file = "aiohttp-3.11.13.tar.gz", hash = "sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, + {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, + {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, + {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, + {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, + {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, + {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, + {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, + {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, + {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, ] [[package]] name = "aiosignal" -version = "1.3.2" +version = "1.4.0" requires_python = ">=3.9" summary = "aiosignal: a list of registered asynchronous callbacks" groups = ["ethereum"] dependencies = [ "frozenlist>=1.1.0", + "typing-extensions>=4.2; python_version < \"3.13\"", ] files = [ - {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, - {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, ] [[package]] @@ -135,9 +140,9 @@ files = [ [[package]] name = "anyio" -version = "4.8.0" +version = "4.10.0" requires_python = ">=3.9" -summary = "High level compatibility layer for multiple asynchronous event loop implementations" +summary = "High-level concurrency and networking framework on top of asyncio or Trio" groups = ["default"] dependencies = [ "exceptiongroup>=1.0.2; python_version < \"3.11\"", @@ -146,8 +151,8 @@ dependencies = [ "typing-extensions>=4.5; python_version < \"3.13\"", ] 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.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] [[package]] @@ -164,219 +169,351 @@ files = [ [[package]] name = "asyncio" -version = "3.4.3" -summary = "reference implementation of PEP 3156" +version = "4.0.0" +requires_python = ">=3.4" +summary = "Deprecated backport of asyncio; use the stdlib package instead" groups = ["default"] files = [ - {file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"}, - {file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"}, + {file = "asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b"}, + {file = "asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b"}, ] [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" requires_python = ">=3.8" summary = "Classes Without Boilerplate" groups = ["ethereum"] files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +requires_python = "<3.11,>=3.8" +summary = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +groups = ["test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, ] [[package]] name = "bitarray" -version = "3.1.1" +version = "3.6.1" summary = "efficient arrays of booleans -- C extension" groups = ["ethereum"] files = [ - {file = "bitarray-3.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb630142c0371862e114cc92fb3c7984b72bbbc4c7e7465225402ab8c7737637"}, - {file = "bitarray-3.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:737755812c8834077885c0843e80eaaa2bf289c149f40a4ad0ed79e848fcbaf3"}, - {file = "bitarray-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a5d705bf86501db0106a25f4558f4954346ab13ac6eff6cb6d680c3f8e7fc9"}, - {file = "bitarray-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:19e04daf81ff027e474e02c1cb5b56faa4180eab60efa60552ca6035957f067d"}, - {file = "bitarray-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9af2df20c620a989a853afa9b01e1be85c9e8c22390aa508d3bbaff1abea27b"}, - {file = "bitarray-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81587e9e3e6a5fc528a3207fd7b2ff5690cf5b041d7786d851ed0618d6a7db55"}, - {file = "bitarray-3.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:121478e4c265ee84d0d154c0e227eee311d9e5885bd36c688b0db5c2c399f7bf"}, - {file = "bitarray-3.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:38fae12b6578585a3ebf917303893d4cc917ddafa5f7dfe4dd3e9a3c7e487786"}, - {file = "bitarray-3.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:210ae85e2a1ea9ee56fbfa749c43e1b4a883eae8676909a3e5c5e07ab6e64a19"}, - {file = "bitarray-3.1.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a190b72836c5ffc31bb33e4d042da74520f4fdb484a3e3b6d4cbb344a220ef4e"}, - {file = "bitarray-3.1.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:08d344b28b91c09d752f8c3737e48de745d1c4c38a82ae1a22d537ab7b401958"}, - {file = "bitarray-3.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:38dea3ce372a7383d1396c46ec816b92738bd50cf14d785770d68565f2234cbd"}, - {file = "bitarray-3.1.1-cp310-cp310-win32.whl", hash = "sha256:76cc30fc59ba83dbc91d56c30325c509e13c7264d98a3e8c80a4be5a429e745f"}, - {file = "bitarray-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:66b9a9e6879733aa435ebf5aaa04adb2b577e09109383f301bf4da506ac08eb0"}, - {file = "bitarray-3.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b4703f0a0b79b9567433f66a736f3d62f5f51e31f653ade93e7ee3f4d53388ab"}, - {file = "bitarray-3.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60b4dd3285ebef22d633d7d6d62dedbe812f5c989718adbfbbca8203072bdda2"}, - {file = "bitarray-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a6c4ca77395248ffdac578328f5375a9adec19a9dafe2609279f47b485c4bd"}, - {file = "bitarray-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:606c19d19cfe6940863e2eb8063524b51fc9422908ac8684782165e287f7a9b0"}, - {file = "bitarray-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327e9831662b94e1fabb4325b405a6aba945862d23e15f4fe2d79a9ab1a3956a"}, - {file = "bitarray-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91474a151d0b891bb4fba59d280f0fc2aa691e4509053b105972471e6e3bdb0a"}, - {file = "bitarray-3.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811825c71ac5c6b6996ee480f1a9290ccb096c539e169349b480db6133748d26"}, - {file = "bitarray-3.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8676f54ba745554f9f989e1d7fa6c39ba1420042755c1259933f206d10bfe20"}, - {file = "bitarray-3.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff12457e6d3123060af83fa442c310550eac11efa0918301afe3caced559e3"}, - {file = "bitarray-3.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5c7ebd040c5aef90608ecf709590900c1ec1131b19b3bd6972218d740ba6ab88"}, - {file = "bitarray-3.1.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ea6babf32bce3a8c5249adb6cde389dd4bacb50f926ebd5171ffea6c9897491a"}, - {file = "bitarray-3.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11fdd645909abbac60bca0579bd62a43d0f22850a2c2659e39509c1ccf96a4b0"}, - {file = "bitarray-3.1.1-cp311-cp311-win32.whl", hash = "sha256:2b27ab40f4b3861b32a380031d7f33952e16b968759446c8be07c64f9035a2d8"}, - {file = "bitarray-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:2080b6110ffa62513774d9140ad6562fd1e478853a46ebe80b6b2cbb13bbfeb2"}, - {file = "bitarray-3.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1af43aa945c595bff4386a2b64c46f6cee653883e295907e419f6745123a168f"}, - {file = "bitarray-3.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e43e1a8b38d6e8ccc27cad74dabd8005b078856c471a1d0537491b71184f209"}, - {file = "bitarray-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3816df3db86feaa8b4efd6844c6808147954626898355253e11e2544145f48"}, - {file = "bitarray-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f913548d2907633782f0c3de72fa28646e1f97bdaf7f2df04014a17e923a258b"}, - {file = "bitarray-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aceaaff83bcdf87bcc13788e29fd068d1a1e6965946ffed80f6e9323e5edd3d"}, - {file = "bitarray-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:079a2fd318d2300c2ef6e84fe9f8f563bcfad46b1360b7d58d82711e5bd8ea56"}, - {file = "bitarray-3.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41f0f93a3ccde6f0e3b953d5adc89f7d4acdd3aadf71af8353c8b288a9d4bd80"}, - {file = "bitarray-3.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51f860b200fa90e446db77fe57173603ddb81eef4d4ba1ccdc277b564b3f20ab"}, - {file = "bitarray-3.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5c819daa6c11107614328d9018ccbaf5b7c64060172bf564373d62928f139e87"}, - {file = "bitarray-3.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:21081fb49d6b0a19b238d30dc0c948bec365bf16fccc2a1478a2794b37a6a812"}, - {file = "bitarray-3.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:514757d0f76d9f84187ea15debd8b3956438f6f4f1e600be1019facdb6076796"}, - {file = "bitarray-3.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a44c5271f7e079530f7a5eecac55cb4ff350ac82db768c550f66c1419893e4c7"}, - {file = "bitarray-3.1.1-cp312-cp312-win32.whl", hash = "sha256:97825938fa3fd7e917e40d66ab70ff133dee385da13cf91e0bd5cd4ec2d97048"}, - {file = "bitarray-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:1dffa70fdf6e4aba4e4f8c8aa408950c3199f71546fb24cafc70f03400b22a59"}, - {file = "bitarray-3.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:20969936312f1f141448debcfd48ec9f64fbf5981b5cd8e01261c8307f7d4be3"}, - {file = "bitarray-3.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e2240b17a3161f5b921a915c2c43091dfb00d8fd495663b31dfe7edbebd7ef6"}, - {file = "bitarray-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71c2cdbb3f625f676f7d1aaab11e29e87195ef07bbf4b5cd7688fc560336a8f1"}, - {file = "bitarray-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4eb3a9c105f7c92c1369fffdd6635344173cf855d9aa1cb74d2485a77ff4c9e7"}, - {file = "bitarray-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db71d42035a1789409975c09c70305930a0b7e0f448aba0a198b2f8505bacd6d"}, - {file = "bitarray-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0948fc9408f8e691d8f83460b1a9376543909bf66a7c7325e9a1974ed045e9c3"}, - {file = "bitarray-3.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22bc5460e2b28674c49842f9c5f47ed68866dbb97d4ee6fc627efce9dda35a6e"}, - {file = "bitarray-3.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ba698f812926a4026929891ecde4f863cb80139bd06581767c77edc63de578c"}, - {file = "bitarray-3.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:651e38e3368b3f79b5aadf2eda04cb3fcb2007513bcee4c658fed2b419be3083"}, - {file = "bitarray-3.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9c3b7e2ffdfbf535268c588c566013b7da99b3e70ff6f968f1a76b32e6252c6a"}, - {file = "bitarray-3.1.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3e305f7ecdac83d83e68c87f3bc0c9be64941e34b58d7b4ad171fbb7c0d0aaaf"}, - {file = "bitarray-3.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b23df9c5b767a58fe6a9ce79f126ebacd8bc4b1c93d9eec8904727baad887a5b"}, - {file = "bitarray-3.1.1-cp313-cp313-win32.whl", hash = "sha256:b9b1109ca43344333a4106b22d70c41c21c559fd8199f09fcfbb9f9e5fd3130b"}, - {file = "bitarray-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:72d6a03b5c27a27ba6a528d511b70f256796ae228c6b97aac9e8c87f53392d3b"}, - {file = "bitarray-3.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e597f014af2538475b0815a035b1d9353eb0206ede61675354ab410026f66948"}, - {file = "bitarray-3.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b658a1cc62b8c6c2375a56b99330c1d66be9352ce299fd649b3f82b055532c07"}, - {file = "bitarray-3.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c96382afde96fe02a138bb5d76bb9cad22bcdfe51f223a0cf855c66c0d866af8"}, - {file = "bitarray-3.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e32b3b2224ef4855c024c9b3a98bc2cd3d2785937472af1c929ada347fe7cb2"}, - {file = "bitarray-3.1.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d5d9d72b6db783d7427e4a5a5b2d48b2ec9df0772d859483a5e31b1231b001"}, - {file = "bitarray-3.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8560480a743341f5720e1ed91234e2199ca4422a6afe849575562aa920468487"}, - {file = "bitarray-3.1.1.tar.gz", hash = "sha256:a3c1d74ac2c969bac33169286fe601f8a6f4ca0e8f26dbaa22ad61fbf8fcf259"}, + {file = "bitarray-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716ec396af5292275f7f572850596990ff84bf1a8552428ee982fe54f773aeb9"}, + {file = "bitarray-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c1d916d36c03c2100af0accc3698091c0bc2e94470cac88462d9f6b56758f37"}, + {file = "bitarray-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:028e95a4792ddef067a5c6151d223089665d5c566cb88f3fe68155e251d8f522"}, + {file = "bitarray-3.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66db701a1c3ea69919a2eb415fb0fce83d55179fd65390399eb40579b102f0a4"}, + {file = "bitarray-3.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cff53ab9cf8d088e88402174ed53529c6ec3897a5146c2a18625253b3b2f6d21"}, + {file = "bitarray-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bb5c678d3957104a33e093427bd514d3b08673e47b358242be6a53d33613ce"}, + {file = "bitarray-3.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3916c63580d9ca9c3264744ce1162e500a6b99c841f25b9657bca2595d2d438"}, + {file = "bitarray-3.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7134fa2d0d253ce1fcd6a5d646f4024ede4a3bf845420bc799dccb430c254ecc"}, + {file = "bitarray-3.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed3009c2ecb7c01b4da8fd0a5e47e0e86a45af746700a6386f247fd922cd3cc2"}, + {file = "bitarray-3.6.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:73cee88d7af7a881447ea593cbaae207be4a472a8c0a9f2da54bcb17512fd75f"}, + {file = "bitarray-3.6.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3abea085790b12c12b3f8ceefae4a7953e14518d88c4b0547187805b999e4158"}, + {file = "bitarray-3.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8aa92c400c813961ff868801ef71a6fa161c6f546433bfb396ee3f596d807a20"}, + {file = "bitarray-3.6.1-cp310-cp310-win32.whl", hash = "sha256:32dc6efc7c87badd7c7941df5ca40346a29532d37bd97a52b67ed563d324b6c7"}, + {file = "bitarray-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ad4e07524372225302e17cb92e7f682baa38fe323e2647dc4f6202304dd8d1a"}, + {file = "bitarray-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52320d72dca8022b4c61bfb5d059743f44421488c0502df623ff87b6d6f48166"}, + {file = "bitarray-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bc717849ea0693b6b93480e499f0da3ea7501f52aaea2b144aa28ac5eecbe62"}, + {file = "bitarray-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00a129b2d790e772fbfb26e31c65f54d5451636654e7f0600af03086aba6461a"}, + {file = "bitarray-3.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9016c534c64342cf3d8c6fda8df681b9771e9fa15a1697423a3766e986d2f958"}, + {file = "bitarray-3.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8404a2c126abdc6cb236347565d4ea622712734fa0987f13c6505b550963b651"}, + {file = "bitarray-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:966478685f59bd429e2425f6a8172a6d8f54fd7d6df48fcae98fd24ff03163d1"}, + {file = "bitarray-3.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41248ab4781a68d5f7ad492d1546b98c2f190c99941de6c92445b693d79123b4"}, + {file = "bitarray-3.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:285a4a39b8d4715bc5165ee3e0bc81bc7f27ef92483bae478dc3d430c0734e82"}, + {file = "bitarray-3.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c9d578179a618b8176fd625dcc3f756f9dc46647c35250798c318e5d4689d096"}, + {file = "bitarray-3.6.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a2581ad5f45eba60bb8d5be324d26052620bb72fd3f76cb6d54d3d0f74859b2"}, + {file = "bitarray-3.6.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:be8f2fd4798539f9b49db517ec9a2e9d1e9132ec92562220f67f80f176900c21"}, + {file = "bitarray-3.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4af03ea3ed535f0c50ed1b871b1c7985188310b26fc5455241a66efb7ac0139f"}, + {file = "bitarray-3.6.1-cp311-cp311-win32.whl", hash = "sha256:1e04d1176f0657fc250ad022c3adc86b52ac7c5352d3d00262b0fc53c376005a"}, + {file = "bitarray-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:eb6ba70e7cf5128ec43787dbc0b5cd661118bf32b756078ff5cb143c9c825d11"}, + {file = "bitarray-3.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a236fc1e87a70adb588b37b09b18add71224279d28140d9ee847778e1f3f5a1a"}, + {file = "bitarray-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d5cf59d8f1ee8332f60e7352464209db1de909ae960d3b1f9d76897e484aa4ed"}, + {file = "bitarray-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38ce28427eea22bafcef073768d7e14d14233ced3eea8505ee13b92fb3723bce"}, + {file = "bitarray-3.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e92011003d87e224e101533a98ede388bb40de0ec65978c6d0bb0d98f949f1b8"}, + {file = "bitarray-3.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f378316f45ffcec4ed429cf2ef446c8a3d7afe29e5020eb51ed789e443f4359f"}, + {file = "bitarray-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b12c11894d991dfaa415229329452e8be2b230e06fba2aff27110158e2f0dafd"}, + {file = "bitarray-3.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f53d6a0ac86d67b6760530196963ea0598588c1a9b155f7e137d9b6a1befd27"}, + {file = "bitarray-3.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b17029647fd990ce6fd3f1fb253ff47bfc27df8255bea99b5e381b2030b6d54"}, + {file = "bitarray-3.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:36cd656877eb3d215ecbb575743d05c521911514985b2a0999a23bb504a8ae64"}, + {file = "bitarray-3.6.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ac29a0cda5ea50c78ff20d06d8c5b8402147448a9dde2b118ecea2b4cec490ec"}, + {file = "bitarray-3.6.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8dceb8d43fe51b8643766152736ec0f32f0a6a5b6e2e6742f1165cbe5799102e"}, + {file = "bitarray-3.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:137bfb9c00c172c16ddabe8615a4746e745789cfedb0e7c5b25236a20ccf051c"}, + {file = "bitarray-3.6.1-cp312-cp312-win32.whl", hash = "sha256:aba6043eb44b68055145c5ae2062f976c02ec0b04ff688ee5b43deda8185b708"}, + {file = "bitarray-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:64a3a8c79468bd5283907f2e60651c857f0dab3dc671943bcf5ec2d15e2f8177"}, + {file = "bitarray-3.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:639fc29267348a78b259fb24c471b7e3322c85f1eb95712bac852ab2a56e742a"}, + {file = "bitarray-3.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357f6c07cb3116a2d4a917fd4f54727f1b71102f5e316c2d4c9fe26ec3dfd8e9"}, + {file = "bitarray-3.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:384eee34bdb4ea52517421cac778d4a881652cd7a5a052bd0939adbca9a6b7d6"}, + {file = "bitarray-3.6.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25cc576ac013b33e69b8123dd7eca78af80100f1a10174e5a30e174ad31f715a"}, + {file = "bitarray-3.6.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84951ce773cbe69e4e15ae27bf6617f08ef2415ad37e1114a2d7979e210bf9e5"}, + {file = "bitarray-3.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c277f6a1cbfdedcec704add6ff2ace89b56a29c1955f2441a7fd2fbde410f791"}, + {file = "bitarray-3.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d8f78bf8bebac391867a7637008ffa68f4870d9ee8154c836745ff237cfa98"}, + {file = "bitarray-3.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b263a460b4ea6a7ebbe450593928651fa81fa3a1426732ff6ea52bb4b210d6c"}, + {file = "bitarray-3.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2a2f08fa831bafb42903231c23df5c597d2cc47fabb2532659180a54386dc49a"}, + {file = "bitarray-3.6.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43d2244aa721dc92713a72695d43dd2a75b920338c9f34da50133d05e23c9ff3"}, + {file = "bitarray-3.6.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:298a0f4442edb98695040009e9d411f221b33cc8d3b749a00d9813a69c047fc1"}, + {file = "bitarray-3.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f16d704c40bd3db661005819daa818c4f5f82823d29491d6744dfc7ff3d6b4a"}, + {file = "bitarray-3.6.1-cp313-cp313-win32.whl", hash = "sha256:a2120ce67c0d0047564c5af1afdd7d03688c2d7109e21ce699742366934c658f"}, + {file = "bitarray-3.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:e3361f1c9e537925284d1f37447a3aad5f5dc1c741cec2ebc078250232d939af"}, + {file = "bitarray-3.6.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a15055d392a921093d3c583e7978acc63fd3f76068a10f8e2deaa078b58a0ac9"}, + {file = "bitarray-3.6.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b6b68a6f7b7b872ea4838d438547ee69a1b020078893f087b35152dd6c3550e"}, + {file = "bitarray-3.6.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26180314ad363dcefa03fff9b7008d8cc2dcc7b080bb38e5bfde545d08e0a7cb"}, + {file = "bitarray-3.6.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae78167bceb3991652dab9d5e66c94e09860005753b51ba608a803a8c2504fb"}, + {file = "bitarray-3.6.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b04a4884e52958d2a5c5e17389fdcc99cb01acbf311a7aa81871a28a2756f89"}, + {file = "bitarray-3.6.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:78a3c6fe40206a3d550a1d10249732152a849d1817fced3e7af5b19f5e832615"}, + {file = "bitarray-3.6.1.tar.gz", hash = "sha256:4255bff37b01562b8e6adcf9db256029765985b0790c5ff76bbe1837edcd53ea"}, +] + +[[package]] +name = "black" +version = "25.1.0" +requires_python = ">=3.9" +summary = "The uncompromising code formatter." +groups = ["lint"] +dependencies = [ + "click>=8.0.0", + "mypy-extensions>=0.4.3", + "packaging>=22.0", + "pathspec>=0.9.0", + "platformdirs>=2", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.0.1; python_version < \"3.11\"", +] +files = [ + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, ] [[package]] name = "certifi" -version = "2025.1.31" -requires_python = ">=3.6" +version = "2025.8.3" +requires_python = ">=3.7" summary = "Python package for providing Mozilla's CA Bundle." groups = ["default", "ethereum"] files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +requires_python = ">=3.8" +summary = "Foreign Function Interface for Python calling C code." +groups = ["default"] +marker = "platform_python_implementation != \"PyPy\"" +dependencies = [ + "pycparser", +] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.3" requires_python = ">=3.7" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." groups = ["ethereum"] files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] name = "ckzg" -version = "2.0.1" +version = "2.1.1" summary = "Python bindings for C-KZG-4844" groups = ["ethereum"] files = [ - {file = "ckzg-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b7f9ba6d215f8981c5545f952aac84875bd564a63da02fb22a3d1321662ecdc0"}, - {file = "ckzg-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8fdec3ff96399acba9baeef9e1b0b5258c08f73245780e6c69f7b73def5e8d0a"}, - {file = "ckzg-2.0.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1644369af9900a9f109d417d6760693edf134118f3100d0c68f56667de775b80"}, - {file = "ckzg-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a2146f122d489ac7e67ae0c0743f8d0db1718e6aeed8f05717340594fe07dd"}, - {file = "ckzg-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979841be50f2782b447762db38e9bc927ae251f6ca86c54a26561a52068ee779"}, - {file = "ckzg-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4516d86647ee4e8ea9470f4adf68fbebb6dc1bdedff7d9592c2504fe53145908"}, - {file = "ckzg-2.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:91866fc58a29b4829201efd9ffadfac3ffeca6359254a54a360ff6a189c34bf5"}, - {file = "ckzg-2.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed35508dac059b2c0a7994383bc7a92eaf35d0b9ce790016819e2619e0f4b8a9"}, - {file = "ckzg-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:449c4fe38017351eca362106420eeb2d28d50b7e54aa8668b3af29a8ab780132"}, - {file = "ckzg-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:260608a22e2f2cadcd31f4495832d45d6460438c38faba9761b92df885a99d88"}, - {file = "ckzg-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1015f99c50215098751b07d7e459ba9a2790d3692ca81552eed29996128e90d"}, - {file = "ckzg-2.0.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dd350d97554c161dc5b8c7b32c2dc8e659632c374f60e2669fb3c9b5b294827"}, - {file = "ckzg-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eec7724fa8dc4ae95757efe4a87e7b2d4b880cb348c72ce7355fc0c4f64bc298"}, - {file = "ckzg-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3fa0f4398fa67fb71f0a2b34a652cc89e6e0e6af1340b0dc771db1a5f3e089c"}, - {file = "ckzg-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f865a0297aabeeb638187a46f7df445763360417b9df4dea60560d512c2cda09"}, - {file = "ckzg-2.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b6ec738350771dbf5974fb70cc8bbb20a4df784af770f7e655922adc08a2171"}, - {file = "ckzg-2.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b4b669fc77edeb16adc182efc32b3737b36f741a2e33a170d40619e8b171a94"}, - {file = "ckzg-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:decb97f4a17c7338b2130dcc4b045df4cc0e7785ece872c764b554c7c73a99ff"}, - {file = "ckzg-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:285cf3121b8a8c5609c5b706314f68d2ba2784ab02c5bb7487c6ae1714ecb27f"}, - {file = "ckzg-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f927bc41c2551b0ef0056a649a7ebed29d9665680a10795f4cee5002c69ddb7"}, - {file = "ckzg-2.0.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd9fb690c88919f30c9f3ab7cc46a7ecd734d5ff4c9ccea383c119b9b7cc4da"}, - {file = "ckzg-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fabc3bd41b306d1c7025d561c3281a007c2aca8ceaf998582dc3894904d9c73e"}, - {file = "ckzg-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb50c53efdb9c34f762bd0c8006cf79bc92a9daf47aa6b541e496988484124f"}, - {file = "ckzg-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7960cc62f959403293fb53a3c2404778369ae7cefc6d7f202e5e00567cf98c4b"}, - {file = "ckzg-2.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d721bcd492294c70eca39da0b0a433c29b6a571dbac2f7084bab06334904af06"}, - {file = "ckzg-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dde2391d025b5033ef0eeacf62b11ecfe446aea25682b5f547a907766ad0a8cb"}, - {file = "ckzg-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fab8859d9420f6f7df4e094ee3639bc49d18c8dab0df81bee825e2363dd67a09"}, - {file = "ckzg-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9747d92883199d4f8f3a3d7018134745fddcf692dfe67115434e4b32609ea785"}, - {file = "ckzg-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b2cf58fb9e165da97f0ffe9f4a6efb73992645fac8e0fa223a6cc7ec486a434a"}, - {file = "ckzg-2.0.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d25d006899d76bb8c9d3e8b27981dd6b66a78f9826e33c1bf981af6577a69a19"}, - {file = "ckzg-2.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04bf0b32f04f5ea5e4b8518e292d3321bc05596fde95f9c3b4f504e5e4bc780"}, - {file = "ckzg-2.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0cf3dccd72376bff10e1833641cc9d642f34f60ca63972626d9dfcfdc8e77f"}, - {file = "ckzg-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:770809c7e93087470cc524724419b0f85590edb033c7c73ba94aef70b36ca18b"}, - {file = "ckzg-2.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e31b59b8124148d5e21f7e41b35532d7af98260c44a77c3917958adece84296d"}, - {file = "ckzg-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:174f0c356df644d6e349ce03b7284d83dbec859e11ca5d1b1b3bace8b8fbc65d"}, - {file = "ckzg-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:30e375cd45142e56b5dbfdec05ce4deb2368d7f7dedfc7408ba37d5639af05ff"}, - {file = "ckzg-2.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a12e96f20dce35e5222f898a5c8355054ef7c5ee038eeb97dbb694640b57577b"}, - {file = "ckzg-2.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4e0ebc55253addaa24dd2cd871bbe3b8f57855f32b5f74e70bf2cb76b6f7da54"}, - {file = "ckzg-2.0.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f917a7bf363a3735db30559e1ed63cf1ccf414234433ba687fa72c007abd756"}, - {file = "ckzg-2.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30f08c984286853271d4adae219e9ba87275a15047dbaa262ab8dd6c01be97b0"}, - {file = "ckzg-2.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fa1ea4888417e1f109fd5e57965788fb7f53b674329b937a65604a3c1ca1d03"}, - {file = "ckzg-2.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0b249914aeaf05cabc71c5c3797e3d6c126cb2c64192b7eb6755ef6aa5ab2f11"}, - {file = "ckzg-2.0.1.tar.gz", hash = "sha256:62c5adc381637affa7e1df465c57750b356a761b8a3164c3106589b02532b9c9"}, + {file = "ckzg-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b9825a1458219e8b4b023012b8ef027ef1f47e903f9541cbca4615f80132730"}, + {file = "ckzg-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2a40a3ba65cca4b52825d26829e6f7eb464aa27a9e9efb6b8b2ce183442c741"}, + {file = "ckzg-2.1.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d753fbe85be7c21602eddc2d40e0915e25fce10329f4f801a0002a4f886cc7"}, + {file = "ckzg-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d76b50527f1d12430bf118aff6fa4051e9860eada43f29177258b8d399448ea"}, + {file = "ckzg-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44c8603e43c021d100f355f50189183135d1df3cbbddb8881552d57fbf421dde"}, + {file = "ckzg-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:38707a638c9d715b3c30b29352b969f78d8fc10faed7db5faf517f04359895c0"}, + {file = "ckzg-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:52c4d257bdcbe822d20c5cd24c8154ec5aac33c49a8f5a19e716d9107a1c8785"}, + {file = "ckzg-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1507f7bfb9bcf51d816db5d8d0f0ed53c8289605137820d437b69daea8333e16"}, + {file = "ckzg-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d02eaaf4f841910133552b3a051dea53bcfe60cd98199fc4cf80b27609d8baa2"}, + {file = "ckzg-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:465e2b71cf9dc383f66f1979269420a0da9274a3a9e98b1a4455e84927dfe491"}, + {file = "ckzg-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee2f26f17a64ad0aab833d637b276f28486b82a29e34f32cf54b237b8f8ab72d"}, + {file = "ckzg-2.1.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99cc2c4e9fb8c62e3e0862c7f4df9142f07ba640da17fded5f6e0fd09f75909f"}, + {file = "ckzg-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773dd016693d74aca1f5d7982db2bad7dde2e147563aeb16a783f7e5f69c01fe"}, + {file = "ckzg-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af2b2144f87ba218d8db01382a961b3ecbdde5ede4fa0d9428d35f8c8a595ba"}, + {file = "ckzg-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f55e63d3f7c934a2cb53728ed1d815479e177aca8c84efe991c2920977cff6"}, + {file = "ckzg-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ecb42aaa0ffa427ff14a9dde9356ba69e5ae6014650b397af55b31bdae7a9b6e"}, + {file = "ckzg-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5a01514239f12fb1a7ad9009c20062a4496e13b09541c1a65f97e295da648c70"}, + {file = "ckzg-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:6516b9684aae262c85cf7fddd8b585b8139ad20e08ec03994e219663abbb0916"}, + {file = "ckzg-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c60e8903344ce98ce036f0fabacce952abb714cad4607198b2f0961c28b8aa72"}, + {file = "ckzg-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4299149dd72448e5a8d2d1cc6cc7472c92fc9d9f00b1377f5b017c089d9cd92"}, + {file = "ckzg-2.1.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:025dd31ffdcc799f3ff842570a2a6683b6c5b01567da0109c0c05d11768729c4"}, + {file = "ckzg-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b42ab8385c273f40a693657c09d2bba40cb4f4666141e263906ba2e519e80bd"}, + {file = "ckzg-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be3890fc1543f4fcfc0063e4baf5c036eb14bcf736dabdc6171ab017e0f1671"}, + {file = "ckzg-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b754210ded172968b201e2d7252573af6bf52d6ad127ddd13d0b9a45a51dae7b"}, + {file = "ckzg-2.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2f8fda87865897a269c4e951e3826c2e814427a6cdfed6731cccfe548f12b36"}, + {file = "ckzg-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:98e70b5923d77c7359432490145e9d1ab0bf873eb5de56ec53f4a551d7eaec79"}, + {file = "ckzg-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:42af7bde4ca45469cd93a96c3d15d69d51d40e7f0d30e3a20711ebd639465fcb"}, + {file = "ckzg-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e4edfdaf87825ff43b9885fabfdea408737a714f4ce5467100d9d1d0a03b673"}, + {file = "ckzg-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:815fd2a87d6d6c57d669fda30c150bc9bf387d47e67d84535aa42b909fdc28ea"}, + {file = "ckzg-2.1.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c32466e809b1ab3ff01d3b0bb0b9912f61dcf72957885615595f75e3f7cc10e5"}, + {file = "ckzg-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f11b73ccf37b12993f39a7dbace159c6d580aacacde6ee17282848476550ddbc"}, + {file = "ckzg-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3b9433a1f2604bd9ac1646d3c83ad84a850d454d3ac589fe8e70c94b38a6b0"}, + {file = "ckzg-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b7d7e1b5ea06234558cd95c483666fd785a629b720a7f1622b3cbffebdc62033"}, + {file = "ckzg-2.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9f5556e6675866040cc4335907be6c537051e7f668da289fa660fdd8a30c9ddb"}, + {file = "ckzg-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55b2ba30c5c9daac0c55f1aac851f1b7bf1f7aa0028c2db4440e963dd5b866d6"}, + {file = "ckzg-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:10d201601fc8f28c0e8cec3406676797024dd374c367bbeec5a7a9eac9147237"}, + {file = "ckzg-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:375918e25eafb9bafe5215ab91698504cba3fe51b4fe92f5896af6c5663f50c6"}, + {file = "ckzg-2.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:38b3b7802c76d4ad015db2b7a79a49c193babae50ee5f77e9ac2865c9e9ddb09"}, + {file = "ckzg-2.1.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438a5009fd254ace0bc1ad974d524547f1a41e6aa5e778c5cd41f4ee3106bcd6"}, + {file = "ckzg-2.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce11cc163a2e0dab3af7455aca7053f9d5bb8d157f231acc7665fd230565d48"}, + {file = "ckzg-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b53964c07f6a076e97eaa1ef35045e935d7040aff14f80bae7e9105717702d05"}, + {file = "ckzg-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cf085f15ae52ab2599c9b5a3d5842794bcf5613b7f58661fbfb0c5d9eac988b9"}, + {file = "ckzg-2.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b0c850bd6cad22ac79b2a2ab884e0e7cd2b54a67d643cd616c145ebdb535a11"}, + {file = "ckzg-2.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:26951f36bb60c9150bbd38110f5e1625596f9779dad54d1d492d8ec38bc84e3a"}, + {file = "ckzg-2.1.1-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe12445e49c4bee67746b7b958e90a973b0de116d0390749b0df351d94e9a8c"}, + {file = "ckzg-2.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71c5d4f66f09de4a99271acac74d2acb3559a77de77a366b34a91e99e8822667"}, + {file = "ckzg-2.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42673c1d007372a4e8b48f6ef8f0ce31a9688a463317a98539757d1e2fb1ecc7"}, + {file = "ckzg-2.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:57a7dc41ec6b69c1d9117eb61cf001295e6b4f67a736020442e71fb4367fb1a5"}, + {file = "ckzg-2.1.1.tar.gz", hash = "sha256:d6b306b7ec93a24e4346aa53d07f7f75053bc0afc7398e35fa649e5f9d48fcc4"}, +] + +[[package]] +name = "click" +version = "8.2.1" +requires_python = ">=3.10" +summary = "Composable command line interface toolkit" +groups = ["lint"] +dependencies = [ + "colorama; platform_system == \"Windows\"", +] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] [[package]] @@ -384,13 +521,62 @@ name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["test"] -marker = "sys_platform == \"win32\"" +groups = ["lint", "test"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cryptography" +version = "45.0.6" +requires_python = "!=3.9.0,!=3.9.1,>=3.7" +summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +groups = ["default"] +dependencies = [ + "cffi>=1.14; platform_python_implementation != \"PyPy\"", +] +files = [ + {file = "cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402"}, + {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42"}, + {file = "cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05"}, + {file = "cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453"}, + {file = "cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159"}, + {file = "cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec"}, + {file = "cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5"}, + {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016"}, + {file = "cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3"}, + {file = "cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9"}, + {file = "cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02"}, + {file = "cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b"}, + {file = "cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012"}, + {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d"}, + {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d"}, + {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da"}, + {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db"}, + {file = "cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18"}, + {file = "cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983"}, + {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427"}, + {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b"}, + {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c"}, + {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385"}, + {file = "cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043"}, + {file = "cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719"}, +] + [[package]] name = "cytoolz" version = "1.0.1" @@ -484,7 +670,7 @@ files = [ [[package]] name = "eth-account" -version = "0.13.5" +version = "0.13.7" requires_python = "<4,>=3.8" summary = "eth-account: Sign Ethereum transactions and messages with local private keys" groups = ["ethereum"] @@ -501,8 +687,8 @@ dependencies = [ "rlp>=1.0.0", ] files = [ - {file = "eth_account-0.13.5-py3-none-any.whl", hash = "sha256:e43fd30c9a7fabb882b50e8c4c41d4486d2f3478ad97c66bb18cfcc872fdbec8"}, - {file = "eth_account-0.13.5.tar.gz", hash = "sha256:010c9ce5f3d2688106cf9bfeb711bb8eaf0154ea6f85325f54fecea85c2b3759"}, + {file = "eth_account-0.13.7-py3-none-any.whl", hash = "sha256:39727de8c94d004ff61d10da7587509c04d2dc7eac71e04830135300bdfc6d24"}, + {file = "eth_account-0.13.7.tar.gz", hash = "sha256:5853ecbcbb22e65411176f121f5f24b8afeeaf13492359d254b16d8b18c77a46"}, ] [[package]] @@ -550,7 +736,7 @@ files = [ [[package]] name = "eth-keys" -version = "0.6.1" +version = "0.7.0" requires_python = "<4,>=3.8" summary = "eth-keys: Common API for Ethereum key operations" groups = ["ethereum"] @@ -559,8 +745,8 @@ dependencies = [ "eth-utils>=2", ] files = [ - {file = "eth_keys-0.6.1-py3-none-any.whl", hash = "sha256:7deae4cd56e862e099ec58b78176232b931c4ea5ecded2f50c7b1ccbc10c24cf"}, - {file = "eth_keys-0.6.1.tar.gz", hash = "sha256:a43e263cbcabfd62fa769168efc6c27b1f5603040e4de22bb84d12567e4fd962"}, + {file = "eth_keys-0.7.0-py3-none-any.whl", hash = "sha256:b0cdda8ffe8e5ba69c7c5ca33f153828edcace844f67aabd4542d7de38b159cf"}, + {file = "eth_keys-0.7.0.tar.gz", hash = "sha256:79d24fd876201df67741de3e3fefb3f4dbcbb6ace66e47e6fe662851a4547814"}, ] [[package]] @@ -582,7 +768,7 @@ files = [ [[package]] name = "eth-typing" -version = "5.2.0" +version = "5.2.1" requires_python = "<4,>=3.8" summary = "eth-typing: Common type annotations for ethereum python packages" groups = ["ethereum"] @@ -590,13 +776,13 @@ dependencies = [ "typing-extensions>=4.5.0", ] files = [ - {file = "eth_typing-5.2.0-py3-none-any.whl", hash = "sha256:e1f424e97990fc3c6a1c05a7b0968caed4e20e9c99a4d5f4db3df418e25ddc80"}, - {file = "eth_typing-5.2.0.tar.gz", hash = "sha256:28685f7e2270ea0d209b75bdef76d8ecef27703e1a16399f6929820d05071c28"}, + {file = "eth_typing-5.2.1-py3-none-any.whl", hash = "sha256:b0c2812ff978267563b80e9d701f487dd926f1d376d674f3b535cfe28b665d3d"}, + {file = "eth_typing-5.2.1.tar.gz", hash = "sha256:7557300dbf02a93c70fa44af352b5c4a58f94e997a0fd6797fb7d1c29d9538ee"}, ] [[package]] name = "eth-utils" -version = "5.2.0" +version = "5.3.0" requires_python = "<4,>=3.8" summary = "eth-utils: Common utility functions for python code that interacts with Ethereum" groups = ["ethereum"] @@ -604,11 +790,12 @@ dependencies = [ "cytoolz>=0.10.1; implementation_name == \"cpython\"", "eth-hash>=0.3.1", "eth-typing>=5.0.0", + "pydantic<3,>=2.0.0", "toolz>0.8.2; implementation_name == \"pypy\"", ] files = [ - {file = "eth_utils-5.2.0-py3-none-any.whl", hash = "sha256:4d43eeb6720e89a042ad5b28d4b2111630ae764f444b85cbafb708d7f076da10"}, - {file = "eth_utils-5.2.0.tar.gz", hash = "sha256:17e474eb654df6e18f20797b22c6caabb77415a996b3ba0f3cc8df3437463134"}, + {file = "eth_utils-5.3.0-py3-none-any.whl", hash = "sha256:ac184883ab299d923428bbe25dae5e356979a3993e0ef695a864db0a20bc262d"}, + {file = "eth_utils-5.3.0.tar.gz", hash = "sha256:1f096867ac6be895f456fa3acb26e9573ae66e753abad9208f316d24d6178156"}, ] [[package]] @@ -624,125 +811,196 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" groups = ["default", "test"] marker = "python_version < \"3.11\"" +dependencies = [ + "typing-extensions>=4.6.0; python_version < \"3.13\"", +] +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[[package]] +name = "flake8" +version = "7.3.0" +requires_python = ">=3.9" +summary = "the modular source code checker: pep8 pyflakes and co" +groups = ["lint"] +dependencies = [ + "mccabe<0.8.0,>=0.7.0", + "pycodestyle<2.15.0,>=2.14.0", + "pyflakes<3.5.0,>=3.4.0", +] files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, +] + +[[package]] +name = "flake8-docstrings" +version = "1.7.0" +requires_python = ">=3.7" +summary = "Extension for flake8 which uses pydocstyle to check docstrings" +groups = ["lint"] +dependencies = [ + "flake8>=3", + "pydocstyle>=2.1", +] +files = [ + {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, + {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, +] + +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +requires_python = ">= 3.6" +summary = "Flake8 plug-in loading the configuration from pyproject.toml" +groups = ["lint"] +dependencies = [ + "Flake8>=5", + "TOMLi; python_version < \"3.11\"", + "TOMLi<2; python_version < \"3.7\"", +] +files = [ + {file = "flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a"}, ] [[package]] name = "frozenlist" -version = "1.5.0" -requires_python = ">=3.8" +version = "1.7.0" +requires_python = ">=3.9" summary = "A list-like structure which implements collections.abc.MutableSequence" groups = ["ethereum"] files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] [[package]] name = "h11" -version = "0.14.0" -requires_python = ">=3.7" +version = "0.16.0" +requires_python = ">=3.8" summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" groups = ["default"] -dependencies = [ - "typing-extensions; python_version < \"3.8\"", -] files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "hexbytes" -version = "1.3.0" +version = "1.3.1" requires_python = "<4,>=3.8" summary = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" groups = ["ethereum"] files = [ - {file = "hexbytes-1.3.0-py3-none-any.whl", hash = "sha256:83720b529c6e15ed21627962938dc2dec9bb1010f17bbbd66bf1e6a8287d522c"}, - {file = "hexbytes-1.3.0.tar.gz", hash = "sha256:4a61840c24b0909a6534350e2d28ee50159ca1c9e89ce275fd31c110312cf684"}, + {file = "hexbytes-1.3.1-py3-none-any.whl", hash = "sha256:da01ff24a1a9a2b1881c4b85f0e9f9b0f51b526b379ffa23832ae7899d29c2c7"}, + {file = "hexbytes-1.3.1.tar.gz", hash = "sha256:a657eebebdfe27254336f98d8af6e2236f3f83aed164b87466b6cf6c5f5a4765"}, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" requires_python = ">=3.8" summary = "A minimal low-level HTTP client." groups = ["default"] dependencies = [ "certifi", - "h11<0.15,>=0.13", + "h11>=0.16", ] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [[package]] @@ -775,13 +1033,24 @@ files = [ [[package]] name = "iniconfig" -version = "2.0.0" -requires_python = ">=3.7" +version = "2.1.0" +requires_python = ">=3.8" summary = "brain-dead simple config-ini parsing" groups = ["test"] 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 = "isort" +version = "6.0.1" +requires_python = ">=3.9.0" +summary = "A Python utility / library to sort Python imports." +groups = ["lint"] +files = [ + {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, + {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, ] [[package]] @@ -795,89 +1064,188 @@ files = [ {file = "jsonalias-0.1.1.tar.gz", hash = "sha256:64f04d935397d579fc94509e1fcb6212f2d081235d9d6395bd10baedf760a769"}, ] +[[package]] +name = "mccabe" +version = "0.7.0" +requires_python = ">=3.6" +summary = "McCabe checker, plugin for flake8" +groups = ["lint"] +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "multidict" -version = "6.1.0" -requires_python = ">=3.8" +version = "6.6.4" +requires_python = ">=3.9" summary = "multidict implementation" groups = ["ethereum"] dependencies = [ "typing-extensions>=4.1.0; python_version < \"3.11\"", ] files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, + {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, + {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, + {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, + {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, + {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, + {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, + {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, + {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, + {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, + {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, + {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, + {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, + {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, + {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, + {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, + {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, + {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, +] + +[[package]] +name = "mypy" +version = "1.17.1" +requires_python = ">=3.9" +summary = "Optional static typing for Python" +groups = ["lint"] +dependencies = [ + "mypy-extensions>=1.0.0", + "pathspec>=0.9.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.6.0", +] +files = [ + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +requires_python = ">=3.8" +summary = "Type system extensions for programs checked with the mypy type checker." +groups = ["lint"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" requires_python = ">=3.8" summary = "Core utilities for Python packages" -groups = ["test"] +groups = ["lint", "test"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -894,255 +1262,366 @@ files = [ ] [[package]] -name = "pluggy" -version = "1.5.0" +name = "pathspec" +version = "0.12.1" requires_python = ">=3.8" +summary = "Utility library for gitignore style pattern matching of file paths." +groups = ["lint"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +requires_python = ">=3.9" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["lint"] +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +requires_python = ">=3.9" summary = "plugin and hook calling mechanisms for python" groups = ["test"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [[package]] name = "propcache" -version = "0.3.0" +version = "0.3.2" requires_python = ">=3.9" summary = "Accelerated property cache" groups = ["ethereum"] files = [ - {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"}, - {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"}, - {file = "propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f"}, - {file = "propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c"}, - {file = "propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340"}, - {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51"}, - {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e"}, - {file = "propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c"}, - {file = "propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d"}, - {file = "propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32"}, - {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e"}, - {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af"}, - {file = "propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c"}, - {file = "propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d"}, - {file = "propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57"}, - {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568"}, - {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9"}, - {file = "propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626"}, - {file = "propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374"}, - {file = "propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a"}, - {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf"}, - {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0"}, - {file = "propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf"}, - {file = "propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863"}, - {file = "propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46"}, - {file = "propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043"}, - {file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +requires_python = ">=3.9" +summary = "Python style guide checker" +groups = ["lint"] +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +requires_python = ">=3.8" +summary = "C parser in Python" +groups = ["default"] +marker = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "pycryptodome" -version = "3.21.0" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +version = "3.23.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cryptographic library for Python" groups = ["ethereum"] files = [ - {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4"}, - {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b"}, - {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e"}, - {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8"}, - {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1"}, - {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a"}, - {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2"}, - {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93"}, - {file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764"}, - {file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6"}, - {file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625"}, + {file = "pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39"}, + {file = "pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27"}, + {file = "pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843"}, + {file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490"}, + {file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575"}, + {file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b"}, + {file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a"}, + {file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f"}, + {file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa"}, + {file = "pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886"}, + {file = "pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2"}, + {file = "pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c"}, + {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379"}, + {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4"}, + {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630"}, + {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353"}, + {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5"}, + {file = "pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef"}, ] [[package]] name = "pydantic" -version = "2.10.6" -requires_python = ">=3.8" +version = "2.11.7" +requires_python = ">=3.9" summary = "Data validation using Python type hints" groups = ["default", "ethereum"] dependencies = [ "annotated-types>=0.6.0", - "pydantic-core==2.27.2", + "pydantic-core==2.33.2", "typing-extensions>=4.12.2", + "typing-inspection>=0.4.0", ] 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.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [[package]] name = "pydantic-core" -version = "2.27.2" -requires_python = ">=3.8" +version = "2.33.2" +requires_python = ">=3.9" summary = "Core functionality for Pydantic validation and serialization" groups = ["default", "ethereum"] dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] 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-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.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [[package]] -name = "pytest" -version = "8.3.5" +name = "pydocstyle" +version = "6.3.0" +requires_python = ">=3.6" +summary = "Python docstring style checker" +groups = ["lint"] +dependencies = [ + "importlib-metadata<5.0.0,>=2.0.0; python_version < \"3.8\"", + "snowballstemmer>=2.2.0", +] +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +requires_python = ">=3.9" +summary = "passive checker of Python programs" +groups = ["lint"] +files = [ + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["test"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[[package]] +name = "pytest" +version = "8.4.1" +requires_python = ">=3.9" summary = "pytest: simple powerful testing with Python" groups = ["test"] dependencies = [ - "colorama; sys_platform == \"win32\"", - "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", - "iniconfig", - "packaging", + "colorama>=0.4; sys_platform == \"win32\"", + "exceptiongroup>=1; python_version < \"3.11\"", + "iniconfig>=1", + "packaging>=20", "pluggy<2,>=1.5", + "pygments>=2.7.2", "tomli>=1; python_version < \"3.11\"", ] files = [ - {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, - {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [[package]] name = "pytest-asyncio" -version = "0.25.3" +version = "1.1.0" requires_python = ">=3.9" summary = "Pytest support for asyncio" groups = ["test"] dependencies = [ + "backports-asyncio-runner<2,>=1.1; python_version < \"3.11\"", "pytest<9,>=8.2", + "typing-extensions>=4.12; python_version < \"3.10\"", ] files = [ - {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, - {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, + {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, + {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, ] [[package]] @@ -1158,99 +1637,112 @@ files = [ [[package]] name = "pywin32" -version = "309" +version = "311" summary = "Python for Window Extensions" groups = ["ethereum"] marker = "platform_system == \"Windows\"" files = [ - {file = "pywin32-309-cp310-cp310-win32.whl", hash = "sha256:5b78d98550ca093a6fe7ab6d71733fbc886e2af9d4876d935e7f6e1cd6577ac9"}, - {file = "pywin32-309-cp310-cp310-win_amd64.whl", hash = "sha256:728d08046f3d65b90d4c77f71b6fbb551699e2005cc31bbffd1febd6a08aa698"}, - {file = "pywin32-309-cp310-cp310-win_arm64.whl", hash = "sha256:c667bcc0a1e6acaca8984eb3e2b6e42696fc035015f99ff8bc6c3db4c09a466a"}, - {file = "pywin32-309-cp311-cp311-win32.whl", hash = "sha256:d5df6faa32b868baf9ade7c9b25337fa5eced28eb1ab89082c8dae9c48e4cd51"}, - {file = "pywin32-309-cp311-cp311-win_amd64.whl", hash = "sha256:e7ec2cef6df0926f8a89fd64959eba591a1eeaf0258082065f7bdbe2121228db"}, - {file = "pywin32-309-cp311-cp311-win_arm64.whl", hash = "sha256:54ee296f6d11db1627216e9b4d4c3231856ed2d9f194c82f26c6cb5650163f4c"}, - {file = "pywin32-309-cp312-cp312-win32.whl", hash = "sha256:de9acacced5fa82f557298b1fed5fef7bd49beee04190f68e1e4783fbdc19926"}, - {file = "pywin32-309-cp312-cp312-win_amd64.whl", hash = "sha256:6ff9eebb77ffc3d59812c68db33c0a7817e1337e3537859499bd27586330fc9e"}, - {file = "pywin32-309-cp312-cp312-win_arm64.whl", hash = "sha256:619f3e0a327b5418d833f44dc87859523635cf339f86071cc65a13c07be3110f"}, - {file = "pywin32-309-cp313-cp313-win32.whl", hash = "sha256:008bffd4afd6de8ca46c6486085414cc898263a21a63c7f860d54c9d02b45c8d"}, - {file = "pywin32-309-cp313-cp313-win_amd64.whl", hash = "sha256:bd0724f58492db4cbfbeb1fcd606495205aa119370c0ddc4f70e5771a3ab768d"}, - {file = "pywin32-309-cp313-cp313-win_arm64.whl", hash = "sha256:8fd9669cfd41863b688a1bc9b1d4d2d76fd4ba2128be50a70b0ea66b8d37953b"}, + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, ] [[package]] name = "regex" -version = "2024.11.6" -requires_python = ">=3.8" +version = "2025.7.34" +requires_python = ">=3.9" summary = "Alternative regular expression module, to replace re." groups = ["ethereum"] files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95b4639c77d414efa93c8de14ce3f7965a94d007e068a94f9d4997bb9bd9c81f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7de1ceed5a5f84f342ba4a9f4ae589524adf9744b2ee61b5da884b5b659834"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02e5860a250cd350c4933cf376c3bc9cb28948e2c96a8bc042aee7b985cfa26f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a5966220b9a1a88691282b7e4350e9599cf65780ca60d914a798cb791aa1177"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48fb045bbd4aab2418dc1ba2088a5e32de4bfe64e1457b948bb328a8dc2f1c2e"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20ff8433fa45e131f7316594efe24d4679c5449c0ca69d91c2f9d21846fdf064"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c436fd1e95c04c19039668cfb548450a37c13f051e8659f40aed426e36b3765f"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b85241d3cfb9f8a13cefdfbd58a2843f208f2ed2c88181bf84e22e0c7fc066d"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:075641c94126b064c65ab86e7e71fc3d63e7ff1bea1fb794f0773c97cdad3a03"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70645cad3407d103d1dbcb4841839d2946f7d36cf38acbd40120fee1682151e5"}, + {file = "regex-2025.7.34-cp310-cp310-win32.whl", hash = "sha256:3b836eb4a95526b263c2a3359308600bd95ce7848ebd3c29af0c37c4f9627cd3"}, + {file = "regex-2025.7.34-cp310-cp310-win_amd64.whl", hash = "sha256:cbfaa401d77334613cf434f723c7e8ba585df162be76474bccc53ae4e5520b3a"}, + {file = "regex-2025.7.34-cp310-cp310-win_arm64.whl", hash = "sha256:bca11d3c38a47c621769433c47f364b44e8043e0de8e482c5968b20ab90a3986"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a"}, + {file = "regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1"}, + {file = "regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a"}, + {file = "regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282"}, + {file = "regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588"}, + {file = "regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62"}, + {file = "regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0"}, + {file = "regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1"}, + {file = "regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997"}, + {file = "regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e"}, + {file = "regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb"}, + {file = "regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae"}, + {file = "regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64"}, + {file = "regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a"}, ] [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" requires_python = ">=3.8" summary = "Python HTTP for Humans." groups = ["ethereum"] @@ -1261,8 +1753,8 @@ dependencies = [ "urllib3<3,>=1.21.1", ] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [[package]] @@ -1290,6 +1782,17 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "snowballstemmer" +version = "3.0.1" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*" +summary = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." +groups = ["lint"] +files = [ + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, +] + [[package]] name = "solders" version = "0.26.0" @@ -1317,7 +1820,7 @@ name = "tomli" version = "2.2.1" requires_python = ">=3.8" summary = "A lil' TOML parser" -groups = ["test"] +groups = ["lint", "test"] marker = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, @@ -1368,7 +1871,7 @@ files = [ [[package]] name = "types-requests" -version = "2.32.0.20250306" +version = "2.32.4.20250809" requires_python = ">=3.9" summary = "Typing stubs for requests" groups = ["ethereum"] @@ -1376,42 +1879,56 @@ dependencies = [ "urllib3>=2", ] files = [ - {file = "types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b"}, - {file = "types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1"}, + {file = "types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}, + {file = "types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}, ] [[package]] name = "typing-extensions" -version = "4.12.2" -requires_python = ">=3.8" -summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["default", "ethereum", "solana"] +version = "4.14.1" +requires_python = ">=3.9" +summary = "Backported and Experimental Type Hints for Python 3.9+" +groups = ["default", "ethereum", "lint", "solana", "test"] 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.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +requires_python = ">=3.9" +summary = "Runtime typing introspection tools" +groups = ["default", "ethereum"] +dependencies = [ + "typing-extensions>=4.12.0", +] +files = [ + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, ] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.5.0" requires_python = ">=3.9" summary = "HTTP library with thread-safe connection pooling, file post, and more." groups = ["ethereum"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [[package]] name = "web3" -version = "7.8.0" +version = "7.13.0" requires_python = "<4,>=3.8" summary = "web3: A Python library for interacting with Ethereum" groups = ["ethereum"] dependencies = [ "aiohttp>=3.7.4.post0", "eth-abi>=5.0.1", - "eth-account>=0.13.1", + "eth-account>=0.13.6", "eth-hash[pycryptodome]>=0.5.1", "eth-typing>=5.0.0", "eth-utils>=5.0.0", @@ -1422,150 +1939,171 @@ dependencies = [ "requests>=2.23.0", "types-requests>=2.0.0", "typing-extensions>=4.0.1", - "websockets<14.0.0,>=10.0.0", + "websockets<16.0.0,>=10.0.0", ] files = [ - {file = "web3-7.8.0-py3-none-any.whl", hash = "sha256:c8771b3d8772f7104a0462804449beb57d36cef7bd8b411140f95a92fc46b559"}, - {file = "web3-7.8.0.tar.gz", hash = "sha256:712bc9fd6b1ef6e467ee24c25b581e1951cab2cba17f9f548f12587734f2c857"}, + {file = "web3-7.13.0-py3-none-any.whl", hash = "sha256:4da7e953300577b7dadbaf430e5fd4479b127b1ad4910234b230fdcb8a49f735"}, + {file = "web3-7.13.0.tar.gz", hash = "sha256:281795e0f5d404c1374e1771f6710bb43e0c975f3141366eb1680763edfb4806"}, ] [[package]] name = "websockets" -version = "13.1" -requires_python = ">=3.8" +version = "15.0.1" +requires_python = ">=3.9" summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" groups = ["ethereum"] files = [ - {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, - {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, - {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, - {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, - {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, - {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, - {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, - {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, - {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, - {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, - {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, - {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, - {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, - {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, - {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, - {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, - {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, + {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, + {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, + {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, + {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, + {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, + {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, + {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, + {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, + {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, + {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] [[package]] name = "yarl" -version = "1.18.3" +version = "1.20.1" requires_python = ">=3.9" summary = "Yet another URL library" groups = ["ethereum"] dependencies = [ "idna>=2.0", "multidict>=4.0", - "propcache>=0.2.0", + "propcache>=0.2.1", ] files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, ] diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index bb8c1e0fd..35e24bcbb 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "httpx>=0.27.2", "asyncio>=3.4.3", "pydantic>=2.9.2", + "cryptography>=43.0.0", ] requires-python = ">=3.10" readme = "README.md" @@ -29,18 +30,119 @@ all = ["solders", "web3"] requires = ["pdm-backend"] build-backend = "pdm.backend" +[dependency-groups] +test = [ + "pytest>=8.3.3", + "pytest-asyncio>=0.24.0", + "evidence-api>=0.5.0", +] +lint = [ + "mypy>=1.8.0", + "black>=24.0.0", + "isort>=5.12.0", + "flake8>=7.0.0", + "flake8-docstrings>=1.7.0", + "flake8-pyproject>=1.2.3", +] +solana = [ + "solders", +] +ethereum = [ + "web3", +] + +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +strict_equality = true +show_error_codes = true + +# Black configuration +[tool.black] +line-length = 88 +target-version = ['py310'] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' + +# isort configuration +[tool.isort] +profile = "black" +line_length = 88 +# Enforce one import per line without parentheses +force_single_line = true +use_parentheses = false +include_trailing_comma = false +multi_line_output = 3 +force_grid_wrap = 0 +ensure_newline_before_comments = true + +# flake8 configuration +[tool.flake8] +max-line-length = 88 +extend-ignore = [ + "E203", # whitespace before ':' + "E501", # line too long (handled by black) + "W503", # line break before binary operator +] +exclude = [ + ".git", + "__pycache__", + "build", + "dist", + ".venv", + ".mypy_cache", + ".pytest_cache", +] +per-file-ignores = [ + "__init__.py:F401", # imported but unused + "tests/*.py:D100,D101,D102,D103,D104", # relax docstring rules for tests +] [tool.pdm] distribution = true [tool.pdm.publish] repository = "pypi" +[tool.pdm.scripts] +# Formatting scripts +fmt = {composite = ["isort src/ tests/", "black src/ tests/"]} +format = {call = "pdm:fmt"} + +# Linting scripts +lint = {composite = ["flake8 src/ tests/", "mypy src/"]} +check = {composite = ["black --check src/ tests/", "isort --check-only src/ tests/", "flake8 src/ tests/", "mypy src/"]} + +# Testing scripts +test = "pytest" +test-cov = "pytest --cov=src/" + +# Combined scripts +check-all = {composite = ["pdm:fmt", "pdm:lint", "pdm:test"]} +verify = {composite = ["pdm:check", "pdm:test"]} + [tool.pdm.dev-dependencies] -test = [ - "pytest>=8.3.3", - "pytest-asyncio>=0.24.0", - "evidence-api>=0.5.0", -] solana = [ "solders", ] diff --git a/sdk/python/src/dstack_sdk/__init__.py b/sdk/python/src/dstack_sdk/__init__.py index aeeb0228e..8be9664cc 100644 --- a/sdk/python/src/dstack_sdk/__init__.py +++ b/sdk/python/src/dstack_sdk/__init__.py @@ -2,6 +2,45 @@ # # SPDX-License-Identifier: Apache-2.0 -from .dstack_client import DstackClient, AsyncDstackClient, GetKeyResponse, GetTlsKeyResponse, GetQuoteResponse +from .dstack_client import AsyncDstackClient +from .dstack_client import AsyncTappdClient +from .dstack_client import DstackClient +from .dstack_client import EventLog +from .dstack_client import GetKeyResponse +from .dstack_client import GetQuoteResponse +from .dstack_client import GetTlsKeyResponse +from .dstack_client import InfoResponse +from .dstack_client import TappdClient +from .dstack_client import TcbInfo +from .encrypt_env_vars import EnvVar +from .encrypt_env_vars import encrypt_env_vars +from .encrypt_env_vars import encrypt_env_vars_sync +from .get_compose_hash import AppCompose +from .get_compose_hash import DockerConfig +from .get_compose_hash import get_compose_hash +from .verify_env_encrypt_public_key import verify_env_encrypt_public_key +from .verify_env_encrypt_public_key import verify_signature_simple -__all__ = ['DstackClient', 'AsyncDstackClient', 'GetKeyResponse', 'GetTlsKeyResponse', 'GetQuoteResponse'] +__all__ = [ + # Core clients + "DstackClient", + "AsyncDstackClient", + "AsyncTappdClient", + "TappdClient", + # Response types + "GetKeyResponse", + "GetTlsKeyResponse", + "GetQuoteResponse", + "InfoResponse", + "TcbInfo", + "EventLog", + # Utility functions + "encrypt_env_vars_sync", + "encrypt_env_vars", + "EnvVar", + "get_compose_hash", + "AppCompose", + "DockerConfig", + "verify_env_encrypt_public_key", + "verify_signature_simple", +] diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 2825e6c8c..fad539138 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -2,90 +2,144 @@ # # SPDX-License-Identifier: Apache-2.0 -from typing import Literal, Optional, List, Dict, Any +import base64 import binascii -import json import hashlib -import os +import json import logging -import base64 +import os +from typing import Any +from typing import Dict +from typing import List +from typing import Optional -from pydantic import BaseModel import httpx +from pydantic import BaseModel -logger = logging.getLogger('dstack_sdk') +logger = logging.getLogger("dstack_sdk") INIT_MR = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" -def replay_rtmr(history: list[str]): - """ - Replay the RTMR history to calculate the final RTMR value. - """ +def replay_rtmr(history: list[str]) -> str: + """Replay RTMR history and return the final RTMR value as hex string.""" if len(history) == 0: return INIT_MR mr = bytes.fromhex(INIT_MR) for content in history: # mr = sha384(concat(mr, content)) # if content is shorter than 48 bytes, pad it with zeros - content = bytes.fromhex(content) - if len(content) < 48: - content = content.ljust(48, b'\0') - mr = hashlib.sha384(mr + content).digest() + content_bytes = bytes.fromhex(content) + if len(content_bytes) < 48: + content_bytes = content_bytes.ljust(48, b"\0") + mr = hashlib.sha384(mr + content_bytes).digest() return mr.hex() def get_endpoint(endpoint: str | None = None) -> str: + """Return the dstack endpoint from argument or environment. + + If ``endpoint`` is not provided, check ``DSTACK_SIMULATOR_ENDPOINT`` in the + environment; otherwise fall back to the default Unix socket path. + """ if endpoint: return endpoint if "DSTACK_SIMULATOR_ENDPOINT" in os.environ: logger.info( - f"Using simulator endpoint: {os.environ['DSTACK_SIMULATOR_ENDPOINT']}") + f"Using simulator endpoint: {os.environ['DSTACK_SIMULATOR_ENDPOINT']}" + ) return os.environ["DSTACK_SIMULATOR_ENDPOINT"] return "/var/run/dstack.sock" +def get_tappd_endpoint(endpoint: str | None = None) -> str: + """Return the tappd endpoint from argument or environment. + + If ``endpoint`` is not provided, check ``TAPPD_SIMULATOR_ENDPOINT`` in the + environment; otherwise fall back to the default Unix socket path. + """ + if endpoint: + return endpoint + if "TAPPD_SIMULATOR_ENDPOINT" in os.environ: + logger.info(f"Using tappd endpoint: {os.environ['TAPPD_SIMULATOR_ENDPOINT']}") + return os.environ["TAPPD_SIMULATOR_ENDPOINT"] + return "/var/run/tappd.sock" + + class GetTlsKeyResponse(BaseModel): + """Response type for TLS key requests.""" + key: str certificate_chain: List[str] + def as_uint8array(self, max_length: Optional[int] = None) -> bytes: + """Return the TLS private key as DER bytes. + + If ``max_length`` is provided, the result is padded/truncated to that + length. + """ + # Remove PEM headers and decode + content = self.key.replace("-----BEGIN PRIVATE KEY-----", "") + content = content.replace("-----END PRIVATE KEY-----", "") + content = content.replace("\n", "").replace(" ", "") + + binary_der = base64.b64decode(content) + + if max_length is None: + return binary_der + else: + # Pad or truncate to max_length + result = bytearray(max_length) + copy_len = min(len(binary_der), max_length) + result[:copy_len] = binary_der[:copy_len] + return bytes(result) + class GetKeyResponse(BaseModel): + """Response type for app key derivation requests.""" + key: str signature_chain: List[str] def decode_key(self) -> bytes: + """Decode the hex key into raw bytes.""" return bytes.fromhex(self.key) def decode_signature_chain(self) -> List[bytes]: + """Decode the signature chain entries from hex to bytes.""" return [bytes.fromhex(chain) for chain in self.signature_chain] class GetQuoteResponse(BaseModel): + """Response type for quote requests, including event log.""" + quote: str event_log: str def decode_quote(self) -> bytes: + """Decode the quote from hex to bytes.""" return bytes.fromhex(self.quote) - def decode_event_log(self) -> 'List[EventLog]': + def decode_event_log(self) -> "List[EventLog]": + """Parse the event log JSON into a list of ``EventLog`` objects.""" return [EventLog(**event) for event in json.loads(self.event_log)] def replay_rtmrs(self) -> Dict[int, str]: - # NOTE: before dstack-0.3.0, event log might not a JSON file. + """Recompute RTMR registers (0-3) from the event log JSON.""" parsed_event_log = json.loads(self.event_log) - rtmrs = {} + rtmrs: Dict[int, str] = {} for idx in range(4): - history = [] - for event in parsed_event_log: - if event.get('imr') == idx: - history.append(event['digest']) + history = [ + event["digest"] for event in parsed_event_log if event.get("imr") == idx + ] rtmrs[idx] = replay_rtmr(history) return rtmrs class EventLog(BaseModel): + """Single event entry included in the quote event log.""" + imr: int event_type: int digest: str @@ -94,6 +148,8 @@ class EventLog(BaseModel): class TcbInfo(BaseModel): + """Trusted computing base information returned by the service.""" + mrtd: str rtmr0: str rtmr1: str @@ -107,6 +163,8 @@ class TcbInfo(BaseModel): class InfoResponse(BaseModel): + """Service information response including TCB info and metadata.""" + app_id: str instance_id: str app_cert: str @@ -118,72 +176,60 @@ class InfoResponse(BaseModel): compose_hash: str @classmethod - def model_validate(cls, obj: Any) -> 'InfoResponse': - if isinstance(obj, dict) and 'tcb_info' in obj and isinstance(obj['tcb_info'], str): + def parse_response(cls, obj: Any) -> "InfoResponse": + """Parse raw JSON into ``InfoResponse``, decoding nested ``tcb_info``.""" + if ( + isinstance(obj, dict) + and "tcb_info" in obj + and isinstance(obj["tcb_info"], str) + ): obj = dict(obj) - obj['tcb_info'] = TcbInfo(**json.loads(obj['tcb_info'])) - return super().model_validate(obj) + obj["tcb_info"] = TcbInfo(**json.loads(obj["tcb_info"])) + return cls(**obj) class BaseClient: - pass + """Marker base class for dstack clients.""" class DstackClient(BaseClient): + """Synchronous client for dstack services.""" + def __init__(self, endpoint: str | None = None): + """Initialize client with HTTP or Unix-socket transport. + + If a non-HTTP(S) endpoint is provided, it is treated as a Unix socket + path and validated for existence. + """ endpoint = get_endpoint(endpoint) - if endpoint.startswith("http://") or endpoint.startswith('https://'): + if endpoint.startswith("http://") or endpoint.startswith("https://"): self.transport = httpx.HTTPTransport() self.base_url = endpoint else: + # Check if Unix socket file exists + if endpoint.startswith("/") and not os.path.exists(endpoint): + raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") self.transport = httpx.HTTPTransport(uds=endpoint) self.base_url = "http://localhost" - def _send_rpc_request(self, path, payload): + def _send_rpc_request(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]: + """Send an RPC request and return the parsed JSON response.""" with httpx.Client(transport=self.transport, base_url=self.base_url) as client: response = client.post( - path, - json=payload, - headers={"Content-Type": "application/json"} + path, json=payload, headers={"Content-Type": "application/json"} ) response.raise_for_status() - return response.json() + from typing import cast + + return cast(Dict[str, Any], response.json()) def get_key( self, path: str | None = None, purpose: str | None = None, ) -> GetKeyResponse: - """ - Derives a key from the given path + app root key. - - Args: - path: Path for key derivation (optional) - purpose: Purpose for key derivation (optional) - - Returns: - GetKeyResponse object containing key and signature chain - - The key derivation process works as follows: - - 1. The app's root key is used as the base for derivation - 2. The provided path is used as context data for HKDF-SHA256 derivation - 3. A derived ECDSA key is created - 4. A message is formed by combining the purpose and the hex-encoded public key of the derived key - 5. The app's key signs this message using Keccak256 (Ethereum-style signing) - 6. The signature includes the recovery ID for verification - 7. A signature chain is returned containing: - - First element: The app key's signature over the derived key's public key - - Second element: The KMS root key's signature over the app's public key - - To verify the key: - 1. Verify the app's signature in the chain using the app's public key - 2. Verify the KMS root key's signature over the app's public key - 3. Verify that the message signed by the app key matches the derived key's public key - 4. Optionally, recover the app's public key from its signature and verify it matches - the expected app public key - """ - data: Dict[str, Any] = {"path": path or '', "purpose": purpose or ''} + """Derive a key from the given path and purpose.""" + data: Dict[str, Any] = {"path": path or "", "purpose": purpose or ""} result = self._send_rpc_request("/GetKey", data) return GetKeyResponse(**result) @@ -191,45 +237,34 @@ def get_quote( self, report_data: str | bytes, ) -> GetQuoteResponse: + """Request an attestation quote for the provided report data.""" if not report_data or not isinstance(report_data, (bytes, str)): raise ValueError("report_data can not be empty") - is_str = isinstance(report_data, str) - if is_str: - report_data = report_data.encode() - if len(report_data) > 64: + report_bytes: bytes = ( + report_data.encode() if isinstance(report_data, str) else report_data + ) + if len(report_bytes) > 64: raise ValueError("report_data must be less than 64 bytes") - hex = binascii.hexlify(report_data).decode() + hex = binascii.hexlify(report_bytes).decode() result = self._send_rpc_request("/GetQuote", {"report_data": hex}) return GetQuoteResponse(**result) def info(self) -> InfoResponse: + """Fetch service information including parsed TCB info.""" result = self._send_rpc_request("/Info", {}) - return InfoResponse.model_validate(result) + return InfoResponse.parse_response(result) def emit_event( self, event: str, payload: str | bytes, ) -> None: - """ - Emit an event. This extends the event to RTMR3 on TDX platform. - - Requires dstack OS 0.5.0 or later. - - Args: - event: The event name - payload: The event data as string or bytes - - Returns: - None - """ + """Emit an event that extends RTMR3 on TDX platforms.""" if not event: raise ValueError("event name cannot be empty") - if isinstance(payload, str): - payload = payload.encode() - - hex_payload = binascii.hexlify(payload).decode() + payload_bytes: bytes = payload.encode() if isinstance(payload, str) else payload + hex_payload = binascii.hexlify(payload_bytes).decode() self._send_rpc_request("/EmitEvent", {"event": event, "payload": hex_payload}) return None @@ -238,22 +273,10 @@ def get_tls_key( subject: str | None = None, alt_names: List[str] | None = None, usage_ra_tls: bool = False, - usage_server_auth: bool = False, + usage_server_auth: bool = True, usage_client_auth: bool = False, ) -> GetTlsKeyResponse: - """ - Gets a TLS key from the dstack service with optional parameters. - - Args: - subject: The subject for the TLS key - alt_names: Alternative names for the TLS key - usage_ra_tls: Whether to enable RA TLS usage - usage_server_auth: Whether to enable server auth usage - usage_client_auth: Whether to enable client auth usage - - Returns: - GetTlsKeyResponse object containing the key and certificate chain - """ + """Request a TLS key from the service with optional parameters.""" data: Dict[str, Any] = { "subject": subject or "", "usage_ra_tls": usage_ra_tls, @@ -261,38 +284,66 @@ def get_tls_key( "usage_client_auth": usage_client_auth, } if alt_names: - data["alt_names"] = alt_names + data["alt_names"] = list(alt_names) result = self._send_rpc_request("/GetTlsKey", data) return GetTlsKeyResponse(**result) + def is_reachable(self) -> bool: + """Return True if the service responds to a quick health call.""" + try: + with httpx.Client( + transport=self.transport, base_url=self.base_url, timeout=0.5 + ) as client: + response = client.post( + "/prpc/Tappd.Info", + json={}, + headers={"Content-Type": "application/json"}, + ) + response.raise_for_status() + return True + except Exception: + return False + class AsyncDstackClient(BaseClient): - def __init__(self, endpoint=None): + """Asynchronous client for dstack services.""" + + def __init__(self, endpoint: str | None = None): + """Initialize async client with HTTP or Unix-socket transport.""" endpoint = get_endpoint(endpoint) - if endpoint.startswith("http://") or endpoint.startswith('https://'): + if endpoint.startswith("http://") or endpoint.startswith("https://"): self.transport = httpx.AsyncHTTPTransport() self.base_url = endpoint else: + # Check if Unix socket file exists + if endpoint.startswith("/") and not os.path.exists(endpoint): + raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") self.transport = httpx.AsyncHTTPTransport(uds=endpoint) self.base_url = "http://localhost" - async def _send_rpc_request(self, path, payload): - async with httpx.AsyncClient(transport=self.transport, base_url=self.base_url) as client: + async def _send_rpc_request( + self, path: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: + """Send an RPC request asynchronously and return parsed JSON.""" + async with httpx.AsyncClient( + transport=self.transport, base_url=self.base_url + ) as client: response = await client.post( - path, - json=payload, - headers={"Content-Type": "application/json"} + path, json=payload, headers={"Content-Type": "application/json"} ) response.raise_for_status() - return response.json() + from typing import cast + + return cast(Dict[str, Any], response.json()) async def get_key( self, path: str | None = None, purpose: str | None = None, ) -> GetKeyResponse: - data: Dict[str, Any] = {"path": path or '', "purpose": purpose or ''} + """Derive a key from the given path and purpose.""" + data: Dict[str, Any] = {"path": path or "", "purpose": purpose or ""} result = await self._send_rpc_request("/GetKey", data) return GetKeyResponse(**result) @@ -300,46 +351,37 @@ async def get_quote( self, report_data: str | bytes, ) -> GetQuoteResponse: + """Request an attestation quote for the provided report data.""" if not report_data or not isinstance(report_data, (bytes, str)): raise ValueError("report_data can not be empty") - is_str = isinstance(report_data, str) - if is_str: - report_data = report_data.encode() - if len(report_data) > 64: + report_bytes: bytes = ( + report_data.encode() if isinstance(report_data, str) else report_data + ) + if len(report_bytes) > 64: raise ValueError("report_data must be less than 64 bytes") - hex = binascii.hexlify(report_data).decode() + hex = binascii.hexlify(report_bytes).decode() result = await self._send_rpc_request("/GetQuote", {"report_data": hex}) return GetQuoteResponse(**result) async def info(self) -> InfoResponse: + """Fetch service information including parsed TCB info.""" result = await self._send_rpc_request("/Info", {}) - return InfoResponse.model_validate(result) + return InfoResponse.parse_response(result) async def emit_event( self, event: str, payload: str | bytes, ) -> None: - """ - Emit an event. This extends the event to RTMR3 on TDX platform. - - Requires dstack OS 0.5.0 or later. - - Args: - event: The event name - payload: The event data as string or bytes - - Returns: - None - """ + """Emit an event that extends RTMR3 on TDX platforms.""" if not event: raise ValueError("event name cannot be empty") - if isinstance(payload, str): - payload = payload.encode() - - hex_payload = binascii.hexlify(payload).decode() - await self._send_rpc_request("/EmitEvent", {"event": event, "payload": hex_payload}) + payload_bytes: bytes = payload.encode() if isinstance(payload, str) else payload + hex_payload = binascii.hexlify(payload_bytes).decode() + await self._send_rpc_request( + "/EmitEvent", {"event": event, "payload": hex_payload} + ) return None async def get_tls_key( @@ -347,22 +389,10 @@ async def get_tls_key( subject: str | None = None, alt_names: List[str] | None = None, usage_ra_tls: bool = False, - usage_server_auth: bool = False, + usage_server_auth: bool = True, usage_client_auth: bool = False, ) -> GetTlsKeyResponse: - """ - Gets a TLS key from the dstack service with optional parameters. - - Args: - subject: The subject for the TLS key - alt_names: Alternative names for the TLS key - usage_ra_tls: Whether to enable RA TLS usage - usage_server_auth: Whether to enable server auth usage - usage_client_auth: Whether to enable client auth usage - - Returns: - GetTlsKeyResponse object containing the key and certificate chain - """ + """Request a TLS key from the service with optional parameters.""" data: Dict[str, Any] = { "subject": subject or "", "usage_ra_tls": usage_ra_tls, @@ -370,7 +400,191 @@ async def get_tls_key( "usage_client_auth": usage_client_auth, } if alt_names: - data["alt_names"] = alt_names + data["alt_names"] = list(alt_names) result = await self._send_rpc_request("/GetTlsKey", data) return GetTlsKeyResponse(**result) + + async def is_reachable(self) -> bool: + """Return True if the service responds to a quick health call.""" + try: + async with httpx.AsyncClient( + transport=self.transport, base_url=self.base_url, timeout=0.5 + ) as client: + response = await client.post( + "/prpc/Tappd.Info", + json={}, + headers={"Content-Type": "application/json"}, + ) + response.raise_for_status() + return True + except Exception: + return False + + +class TappdClient(DstackClient): + """Deprecated client kept for backward compatibility. + + DEPRECATED: Use ``DstackClient`` instead. + """ + + def __init__(self, endpoint: str | None = None): + """Initialize deprecated tappd client wrapper.""" + import warnings + + warnings.warn( + "TappdClient is deprecated, please use DstackClient instead", + DeprecationWarning, + stacklevel=2, + ) + + endpoint = get_tappd_endpoint(endpoint) + super().__init__(endpoint) + + def derive_key( + self, + path: str | None = None, + subject: str | None = None, + alt_names: List[str] | None = None, + ) -> GetTlsKeyResponse: + """Use ``get_key`` instead (deprecated).""" + import warnings + + warnings.warn( + "derive_key is deprecated, please use get_key instead", + DeprecationWarning, + stacklevel=2, + ) + + data: Dict[str, Any] = { + "path": path or "", + "subject": subject or path or "", + } + if alt_names: + data["alt_names"] = alt_names + + result = self._send_rpc_request("/prpc/Tappd.DeriveKey", data) + return GetTlsKeyResponse(**result) + + def tdx_quote( + self, + report_data: str | bytes, + hash_algorithm: str | None = None, + ) -> GetQuoteResponse: + """Use ``get_quote`` instead (deprecated).""" + import warnings + + warnings.warn( + "tdx_quote is deprecated, please use get_quote instead", + DeprecationWarning, + stacklevel=2, + ) + + if not report_data or not isinstance(report_data, (bytes, str)): + raise ValueError("report_data can not be empty") + + report_bytes: bytes = ( + report_data.encode() if isinstance(report_data, str) else report_data + ) + hex_data = binascii.hexlify(report_bytes).decode() + + if hash_algorithm == "raw": + if len(hex_data) > 128: + raise ValueError( + "Report data is too large, it should less then 64 bytes when hash_algorithm is raw." + ) + if len(hex_data) < 128: + hex_data = hex_data.zfill(128) + + payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} + + result = self._send_rpc_request("/prpc/Tappd.TdxQuote", payload) + + if "error" in result: + raise RuntimeError(result["error"]) + + return GetQuoteResponse(**result) + + +class AsyncTappdClient(AsyncDstackClient): + """Deprecated async client kept for backward compatibility. + + DEPRECATED: Use ``AsyncDstackClient`` instead. + """ + + def __init__(self, endpoint: str | None = None): + """Initialize deprecated async tappd client wrapper.""" + import warnings + + warnings.warn( + "AsyncTappdClient is deprecated, please use AsyncDstackClient instead", + DeprecationWarning, + stacklevel=2, + ) + + endpoint = get_tappd_endpoint(endpoint) + super().__init__(endpoint) + + async def derive_key( + self, + path: str | None = None, + subject: str | None = None, + alt_names: List[str] | None = None, + ) -> GetTlsKeyResponse: + """Use ``get_key`` instead (deprecated).""" + import warnings + + warnings.warn( + "derive_key is deprecated, please use get_key instead", + DeprecationWarning, + stacklevel=2, + ) + + data: Dict[str, Any] = { + "path": path or "", + "subject": subject or path or "", + } + if alt_names: + data["alt_names"] = alt_names + + result = await self._send_rpc_request("/prpc/Tappd.DeriveKey", data) + return GetTlsKeyResponse(**result) + + async def tdx_quote( + self, + report_data: str | bytes, + hash_algorithm: str | None = None, + ) -> GetQuoteResponse: + """Use ``get_quote`` instead (deprecated).""" + import warnings + + warnings.warn( + "tdx_quote is deprecated, please use get_quote instead", + DeprecationWarning, + stacklevel=2, + ) + + if not report_data or not isinstance(report_data, (bytes, str)): + raise ValueError("report_data can not be empty") + + report_bytes: bytes = ( + report_data.encode() if isinstance(report_data, str) else report_data + ) + hex_data = binascii.hexlify(report_bytes).decode() + + if hash_algorithm == "raw": + if len(hex_data) > 128: + raise ValueError( + "Report data is too large, it should less then 64 bytes when hash_algorithm is raw." + ) + if len(hex_data) < 128: + hex_data = hex_data.zfill(128) + + payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} + + result = await self._send_rpc_request("/prpc/Tappd.TdxQuote", payload) + + if "error" in result: + raise RuntimeError(result["error"]) + + return GetQuoteResponse(**result) diff --git a/sdk/python/src/dstack_sdk/encrypt_env_vars.py b/sdk/python/src/dstack_sdk/encrypt_env_vars.py new file mode 100644 index 000000000..685e9e2eb --- /dev/null +++ b/sdk/python/src/dstack_sdk/encrypt_env_vars.py @@ -0,0 +1,101 @@ +""" +Environment variable encryption module for dstack SDK. + +Provides functionality to encrypt environment variables using X25519 key exchange +and AES-GCM encryption, similar to the TypeScript implementation. +""" + +import json +import secrets +from typing import List +from typing import NamedTuple + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + + +class EnvVar(NamedTuple): + """Environment variable key-value pair.""" + + key: str + value: str + + +def hex_to_bytes(hex_str: str) -> bytes: + """Convert hex string to bytes.""" + if hex_str.startswith("0x"): + hex_str = hex_str[2:] + return bytes.fromhex(hex_str) + + +def bytes_to_hex(data: bytes) -> str: + """Convert bytes to hex string.""" + return data.hex() + + +async def encrypt_env_vars(envs: List[EnvVar], public_key_hex: str) -> str: + """Encrypt environment variables using X25519 and AES-GCM. + + - Generate an ephemeral keypair and compute a shared secret with the remote + public key + - Encrypt the JSON payload with AES-GCM using the shared secret + - Return concatenation of ephemeral public key + IV + ciphertext as hex + """ + # Prepare environment data as JSON + env_dict = {"env": [{"key": env.key, "value": env.value} for env in envs]} + env_json = json.dumps(env_dict).encode("utf-8") + + # Generate private key and derive public key + private_key = X25519PrivateKey.generate() + public_key = private_key.public_key() + + # Get public key bytes (32 bytes for X25519) + public_key_bytes = public_key.public_bytes( + encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw + ) + + # Generate shared secret + remote_public_key = X25519PublicKey.from_public_bytes(hex_to_bytes(public_key_hex)) + shared_secret = private_key.exchange(remote_public_key) + + # Encrypt the data using AES-GCM + aesgcm = AESGCM(shared_secret) + iv = secrets.token_bytes(12) # 12 bytes IV for GCM + encrypted_data = aesgcm.encrypt(iv, env_json, None) + + # Combine all components: public_key + iv + encrypted_data + result = public_key_bytes + iv + encrypted_data + + return bytes_to_hex(result) + + +def encrypt_env_vars_sync(envs: List[EnvVar], public_key_hex: str) -> str: + """Encrypt environment variables synchronously using X25519 and AES-GCM.""" + # Prepare environment data as JSON + env_dict = {"env": [{"key": env.key, "value": env.value} for env in envs]} + env_json = json.dumps(env_dict).encode("utf-8") + + # Generate private key and derive public key + private_key = X25519PrivateKey.generate() + public_key = private_key.public_key() + + # Get public key bytes (32 bytes for X25519) + public_key_bytes = public_key.public_bytes( + encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw + ) + + # Generate shared secret + remote_public_key = X25519PublicKey.from_public_bytes(hex_to_bytes(public_key_hex)) + shared_secret = private_key.exchange(remote_public_key) + + # Encrypt the data using AES-GCM + aesgcm = AESGCM(shared_secret) + iv = secrets.token_bytes(12) # 12 bytes IV for GCM + encrypted_data = aesgcm.encrypt(iv, env_json, None) + + # Combine all components: public_key + iv + encrypted_data + result = public_key_bytes + iv + encrypted_data + + return bytes_to_hex(result) diff --git a/sdk/python/src/dstack_sdk/ethereum.py b/sdk/python/src/dstack_sdk/ethereum.py index c4680aa09..0d18b9619 100644 --- a/sdk/python/src/dstack_sdk/ethereum.py +++ b/sdk/python/src/dstack_sdk/ethereum.py @@ -2,9 +2,63 @@ # # SPDX-License-Identifier: Apache-2.0 +"""Ethereum helpers for deriving accounts from dstack keys. + +Use with ``dstack_sdk.DstackClient`` responses to create ``eth_account`` +objects for signing and transacting. +""" +import warnings + from eth_account import Account +from eth_account.signers.local import LocalAccount from .dstack_client import GetKeyResponse +from .dstack_client import GetTlsKeyResponse + + +def to_account(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> LocalAccount: + """ + Create an Ethereum account from DstackClient key response. + + DEPRECATED: Use to_account_secure instead. This method has security concerns. + Current implementation uses raw key material without proper hashing. + + Args: + get_key_response: Response from get_key() or get_tls_key() + + Returns: + Account: Ethereum account object + """ + if hasattr(get_key_response, "as_uint8array"): # GetTlsKeyResponse + warnings.warn( + "to_account: Please don't use getTlsKey method to get key, use getKey instead.", + DeprecationWarning, + stacklevel=2, + ) + key_bytes = get_key_response.as_uint8array(32) + return Account.from_key(key_bytes) # type: ignore[no-any-return] + else: # GetKeyResponse + return Account.from_key(get_key_response.decode_key()) # type: ignore[no-any-return] + -def to_account(get_key_response: GetKeyResponse) -> Account: - return Account.from_key(get_key_response.decode_key()) +def to_account_secure( + get_key_response: GetKeyResponse | GetTlsKeyResponse, +) -> LocalAccount: + """Create an Ethereum account using SHA256 of full key material for security.""" + if hasattr(get_key_response, "as_uint8array"): # GetTlsKeyResponse + warnings.warn( + "to_account_secure: Please don't use getTlsKey method to get key, use getKey instead.", + DeprecationWarning, + stacklevel=2, + ) + try: + # Hash the complete key material with SHA256 + key_bytes = get_key_response.as_uint8array() + hashed_key = hashlib.sha256(key_bytes).digest() + return Account.from_key(hashed_key) # type: ignore[no-any-return] + except Exception as e: + raise RuntimeError( + "to_account_secure: missing SHA256 support, please upgrade your system" + ) from e + else: # GetKeyResponse + return Account.from_key(get_key_response.decode_key()) # type: ignore[no-any-return] diff --git a/sdk/python/src/dstack_sdk/get_compose_hash.py b/sdk/python/src/dstack_sdk/get_compose_hash.py new file mode 100644 index 000000000..683b3b90b --- /dev/null +++ b/sdk/python/src/dstack_sdk/get_compose_hash.py @@ -0,0 +1,240 @@ +""" +Compose hash calculation module for dstack SDK. + +Provides deterministic JSON serialization and SHA256 hashing of AppCompose configurations, +compatible with the TypeScript implementation. +""" + +import hashlib +import json +from typing import Any +from typing import Dict +from typing import List +from typing import Literal +from typing import Optional +from typing import Union + +KeyProviderKind = Literal["none", "kms", "local"] + + +class DockerConfig: + """Docker configuration for app compose.""" + + def __init__( + self, + registry: Optional[str] = None, + username: Optional[str] = None, + token_key: Optional[str] = None, + ) -> None: + """Initialize a new ``DockerConfig`` instance.""" + self.registry = registry + self.username = username + self.token_key = token_key + + def to_dict(self) -> Dict[str, Any]: + """Return a dictionary representation excluding ``None`` fields.""" + result: Dict[str, Any] = {} + if self.registry is not None: + result["registry"] = self.registry + if self.username is not None: + result["username"] = self.username + if self.token_key is not None: + result["token_key"] = self.token_key + return result + + +class AppCompose: + """App compose configuration.""" + + def __init__( + self, + runner: str, + manifest_version: Optional[int] = None, + name: Optional[str] = None, + features: Optional[List[str]] = None, # Deprecated + docker_compose_file: Optional[str] = None, + docker_config: Optional[DockerConfig] = None, + public_logs: Optional[bool] = None, + public_sysinfo: Optional[bool] = None, + public_tcbinfo: Optional[bool] = None, + kms_enabled: Optional[bool] = None, + gateway_enabled: Optional[bool] = None, + tproxy_enabled: Optional[bool] = None, # For backward compatibility + local_key_provider_enabled: Optional[bool] = None, + key_provider: Optional[KeyProviderKind] = None, + key_provider_id: Optional[str] = None, + allowed_envs: Optional[List[str]] = None, + no_instance_id: Optional[bool] = None, + secure_time: Optional[bool] = None, + bash_script: Optional[str] = None, # Legacy + pre_launch_script: Optional[str] = None, # Legacy + **kwargs: Any, + ) -> None: + """Initialize a new ``AppCompose`` instance with arbitrary extra fields.""" + self.runner = runner + self.manifest_version = manifest_version + self.name = name + self.features = features + self.docker_compose_file = docker_compose_file + self.docker_config = docker_config + self.public_logs = public_logs + self.public_sysinfo = public_sysinfo + self.public_tcbinfo = public_tcbinfo + self.kms_enabled = kms_enabled + self.gateway_enabled = gateway_enabled + self.tproxy_enabled = tproxy_enabled + self.local_key_provider_enabled = local_key_provider_enabled + self.key_provider = key_provider + self.key_provider_id = key_provider_id + self.allowed_envs = allowed_envs + self.no_instance_id = no_instance_id + self.secure_time = secure_time + self.bash_script = bash_script + self.pre_launch_script = pre_launch_script + + # Add any additional fields + for key, value in kwargs.items(): + setattr(self, key, value) + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary representation.""" + result: Dict[str, Any] = {} + + # Add all attributes that are not None + for attr_name in dir(self): + if not attr_name.startswith("_") and not callable(getattr(self, attr_name)): + value = getattr(self, attr_name) + if value is not None: + if isinstance(value, DockerConfig): + result[attr_name] = value.to_dict() + else: + # Handle special float values + if isinstance(value, float): + if value != value: # NaN check + result[attr_name] = None + elif value == float("inf") or value == float("-inf"): + result[attr_name] = None + else: + result[attr_name] = value + else: + result[attr_name] = value + + return result + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "AppCompose": + """Create AppCompose from dictionary.""" + # Handle docker_config + docker_config: Optional[DockerConfig] = None + if "docker_config" in data and data["docker_config"] is not None: + dc = data.pop("docker_config") + docker_config = DockerConfig(**dc) + + # Handle special float values + processed_data: Dict[str, Any] = {} + for key, value in data.items(): + if isinstance(value, float): + if value != value: # NaN check + processed_data[key] = None + elif value == float("inf") or value == float("-inf"): + processed_data[key] = None + else: + processed_data[key] = value + else: + processed_data[key] = value + + runner_value = processed_data.pop("runner") + runner: str = str(runner_value) + return cls(runner=runner, docker_config=docker_config, **processed_data) + + +def sort_object(obj: Any) -> Any: + """ + Recursively sort object keys lexicographically. + + This is crucial for deterministic JSON.stringify. + """ + if obj is None: + return obj + elif isinstance(obj, list): + return [sort_object(item) for item in obj] + elif isinstance(obj, dict): + return {key: sort_object(value) for key, value in sorted(obj.items())} + else: + return obj + + +def preprocess_app_compose(app_compose: AppCompose) -> AppCompose: + """Preprocess app compose by removing conflicting fields based on runner.""" + # Create a copy + data = app_compose.to_dict() + + if data.get("runner") == "bash" and "docker_compose_file" in data: + del data["docker_compose_file"] + elif data.get("runner") == "docker-compose" and "bash_script" in data: + del data["bash_script"] + + if "pre_launch_script" in data and not data["pre_launch_script"]: + del data["pre_launch_script"] + + return AppCompose.from_dict(data) + + +def to_deterministic_json(app_compose: AppCompose) -> str: + """ + Serialize to deterministic JSON following cross-language standards. + + - Recursively sorts object keys lexicographically + - Compact output (no spaces) + - Handles special values (NaN, Infinity) by converting them to null + - UTF-8 encoding (default in Python) + """ + data = sort_object(app_compose.to_dict()) + + def convert_special_values(obj: Any) -> Any: + """Convert NaN and Infinity to null for deterministic output.""" + if isinstance(obj, float): + if obj != obj: # NaN check + return None + if obj == float("inf") or obj == float("-inf"): + return None + return obj + + def json_serializer(obj: Any) -> Any: + """Handle special float values during JSON serialization.""" + return convert_special_values(obj) + + # Convert special values recursively + def process_data(obj: Any) -> Any: + if isinstance(obj, dict): + return {key: process_data(value) for key, value in obj.items()} + elif isinstance(obj, list): + return [process_data(item) for item in obj] + else: + return convert_special_values(obj) + + processed_data = process_data(data) + return json.dumps(processed_data, separators=(",", ":"), ensure_ascii=False) + + +def get_compose_hash( + app_compose: Union[AppCompose, Dict[str, Any]], normalize: bool = False +) -> str: + """ + Calculate SHA256 hash of app compose configuration. + + Args: + app_compose: AppCompose object or dictionary + normalize: Whether to preprocess the compose (remove conflicting fields) + + Returns: + str: SHA256 hash as hex string + """ + if isinstance(app_compose, dict): + app_compose = AppCompose.from_dict(app_compose) + + if normalize: + app_compose = preprocess_app_compose(app_compose) + + manifest_str = to_deterministic_json(app_compose) + return hashlib.sha256(manifest_str.encode("utf-8")).hexdigest() diff --git a/sdk/python/src/dstack_sdk/solana.py b/sdk/python/src/dstack_sdk/solana.py index ee536cd19..c8506bf2c 100644 --- a/sdk/python/src/dstack_sdk/solana.py +++ b/sdk/python/src/dstack_sdk/solana.py @@ -2,9 +2,61 @@ # # SPDX-License-Identifier: Apache-2.0 +"""Solana helpers for deriving keypairs from dstack keys. + +Use with ``dstack_sdk.DstackClient`` responses to create ``solders.Keypair`` +objects for signing transactions on Solana. +""" +import warnings + from solders.keypair import Keypair from .dstack_client import GetKeyResponse +from .dstack_client import GetTlsKeyResponse + + +def to_keypair(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> Keypair: + """ + Create a Solana Keypair from DstackClient key response. + + DEPRECATED: Use to_keypair_secure instead. This method has security concerns. + Current implementation uses raw key material without proper hashing. + + Args: + get_key_response: Response from get_key() or get_tls_key() + + Returns: + Keypair: Solana keypair object + """ + if isinstance(get_key_response, GetTlsKeyResponse): + warnings.warn( + "to_keypair: Please don't use getTlsKey method to get key, use getKey instead.", + DeprecationWarning, + stacklevel=2, + ) + # Restored original behavior: using first 32 bytes directly + key_bytes = get_key_response.as_uint8array(32) + return Keypair.from_seed(key_bytes) + else: # GetKeyResponse + return Keypair.from_seed(get_key_response.decode_key()) + -def to_keypair(get_key_response: GetKeyResponse) -> Keypair: - return Keypair.from_seed(get_key_response.decode_key()) +def to_keypair_secure(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> Keypair: + """Create a Solana Keypair using SHA256 of full key material for security.""" + if isinstance(get_key_response, GetTlsKeyResponse): + warnings.warn( + "to_keypair_secure: Please don't use getTlsKey method to get key, use getKey instead.", + DeprecationWarning, + stacklevel=2, + ) + try: + # Hash the complete key material with SHA256 + key_bytes = get_key_response.as_uint8array() + hashed_key = hashlib.sha256(key_bytes).digest() + return Keypair.from_seed(hashed_key) + except Exception as e: + raise RuntimeError( + "to_keypair_secure: missing SHA256 support, please upgrade your system" + ) from e + else: # GetKeyResponse + return Keypair.from_seed(get_key_response.decode_key()) diff --git a/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py b/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py new file mode 100644 index 000000000..120d37dc3 --- /dev/null +++ b/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py @@ -0,0 +1,119 @@ +"""Verify ECDSA signatures of environment-encrypt public keys. + +This module prepares the message per dstack convention and offers a simplified +API surface. Full public key recovery is not implemented. +""" + +import hashlib +from typing import Optional + + +def verify_env_encrypt_public_key( + public_key: bytes, signature: bytes, app_id: str +) -> Optional[str]: + """Attempt public key recovery from signature; return compressed key or None.""" + if len(signature) != 65: + return None + + try: + # Create the message to verify + prefix = b"dstack-env-encrypt-pubkey" + + # Remove 0x prefix if present + clean_app_id = app_id[2:] if app_id.startswith("0x") else app_id + + # Validate hex string + try: + app_id_bytes = bytes.fromhex(clean_app_id) + except ValueError: + # Invalid hex string, return None + return None + + separator = b":" + + # Construct message: prefix + ":" + app_id + public_key + message = prefix + separator + app_id_bytes + public_key + + # Hash the message with SHA3-256 (Keccak-256) + # Note: Using hashlib.sha3_256 which is actually Keccak-256 in most implementations + message_hash = hashlib.sha3_256(message).digest() + + # Extract r, s, recovery_id from signature + r = signature[:32] + s = signature[32:64] + recovery_id = signature[64] + + # Convert r, s to integers + r_int = int.from_bytes(r, byteorder="big") + s_int = int.from_bytes(s, byteorder="big") + + # Recover public key from signature + # This is a simplified version - for full ECDSA recovery, + # we need more complex logic to try different recovery possibilities + + # For now, let's try a basic approach using known cryptographic libraries + # This is where we would typically use a library like ethereum's ecrecover + # Since we don't have that, let's implement a basic verification + + # Try both possible recovery IDs (0 and 1, or adjusted by 27) + for recovery_attempt in [recovery_id, recovery_id ^ 1]: + # This is a stub. A proper ECDSA recovery implementation is required + # to return a real public key. For now, always continue. + recovered_key = _recover_public_key( + message_hash, r_int, s_int, recovery_attempt + ) + if recovered_key: + return f"0x{recovered_key.hex()}" + + return None + + except Exception: + # Keep behavior non-fatal; callers treat None as invalid + # Avoid bare except to satisfy lint rules + return None + + +def _recover_public_key( + message_hash: bytes, r: int, s: int, recovery_id: int +) -> Optional[bytes]: + """ + Recover public key from ECDSA signature components. + + This is a simplified implementation. In production, you should use + a proper ECDSA recovery library like the one used in Ethereum. + """ + # Public key recovery is not implemented in this simplified version. + return None + + +# Alternative implementation using a more direct approach +def verify_signature_simple(public_key: bytes, signature: bytes, app_id: str) -> bool: + """Perform simple signature preprocessing without ECDSA recovery.""" + if len(signature) != 65: + return False + + try: + # Create the message + prefix = b"dstack-env-encrypt-pubkey" + clean_app_id = app_id[2:] if app_id.startswith("0x") else app_id + + # Validate hex string + try: + app_id_bytes = bytes.fromhex(clean_app_id) + except ValueError: + # Invalid hex string + return False + + separator = b":" + message = prefix + separator + app_id_bytes + public_key + + # Hash with SHA3-256 (Keccak-256) + # Compute hash to mirror verification flow, but unused in the stub + _ = hashlib.sha3_256(message).digest() + + # For now, return True to indicate the message was processed correctly + # Full signature verification would require ECDSA implementation + return True + + except Exception: + return False diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 3efd9ba28..89393f8f7 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -2,14 +2,18 @@ # # SPDX-License-Identifier: Apache-2.0 -from dstack_sdk.dstack_client import TcbInfo -from dstack_sdk.dstack_client import InfoResponse -import hashlib import pytest - from evidence_api.tdx.quote import TdxQuote -from dstack_sdk import DstackClient, AsyncDstackClient, GetKeyResponse, GetQuoteResponse, GetTlsKeyResponse +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient +from dstack_sdk import GetKeyResponse +from dstack_sdk import GetQuoteResponse +from dstack_sdk import GetTlsKeyResponse +from dstack_sdk import TappdClient +from dstack_sdk.dstack_client import InfoResponse +from dstack_sdk.dstack_client import TcbInfo + def test_sync_client_get_key(): client = DstackClient() @@ -18,11 +22,13 @@ def test_sync_client_get_key(): assert isinstance(result.decode_key(), bytes) assert len(result.decode_key()) == 32 + def test_sync_client_get_quote(): client = DstackClient() - result = client.get_quote('test') + result = client.get_quote("test") assert isinstance(result, GetQuoteResponse) + def test_sync_client_get_tls_key(): client = DstackClient() result = client.get_tls_key() @@ -31,11 +37,13 @@ def test_sync_client_get_tls_key(): assert len(result.key) > 0 assert len(result.certificate_chain) > 0 + def test_sync_client_get_info(): client = DstackClient() result = client.info() check_info_response(result) + def check_info_response(result: InfoResponse): assert isinstance(result, InfoResponse) assert isinstance(result.app_id, str) @@ -51,33 +59,38 @@ def check_info_response(result: InfoResponse): assert len(result.tcb_info.app_compose) > 0 assert len(result.tcb_info.event_log) > 0 + @pytest.mark.asyncio async def test_async_client_get_key(): client = AsyncDstackClient() result = await client.get_key() assert isinstance(result, GetKeyResponse) + @pytest.mark.asyncio async def test_async_client_get_quote(): client = AsyncDstackClient() - result = await client.get_quote('test') + result = await client.get_quote("test") assert isinstance(result, GetQuoteResponse) + @pytest.mark.asyncio async def test_async_client_get_tls_key(): client = AsyncDstackClient() result = await client.get_tls_key() assert isinstance(result, GetTlsKeyResponse) assert isinstance(result.key, str) - assert result.key.startswith('-----BEGIN PRIVATE KEY-----') + assert result.key.startswith("-----BEGIN PRIVATE KEY-----") assert len(result.certificate_chain) > 0 + @pytest.mark.asyncio async def test_async_client_get_info(): client = AsyncDstackClient() result = await client.info() check_info_response(result) + @pytest.mark.asyncio async def test_tls_key_uniqueness(): """Test that TLS keys are unique across multiple calls.""" @@ -87,10 +100,11 @@ async def test_tls_key_uniqueness(): # TLS keys should be unique for each call assert result1.key != result2.key + @pytest.mark.asyncio async def test_replay_rtmr(): client = AsyncDstackClient() - result = await client.get_quote('test') + result = await client.get_quote("test") # TODO evidence_api is a bit out-of-date, we need an up-to-date implementation. tdxQuote = TdxQuote(bytes.fromhex(result.quote)) rtmrs = result.replay_rtmrs() @@ -99,22 +113,185 @@ async def test_replay_rtmr(): assert rtmrs[2] == tdxQuote.body.rtmr2.hex() assert rtmrs[3] == tdxQuote.body.rtmr3.hex() + @pytest.mark.asyncio async def test_get_quote_raw_hash_error(): with pytest.raises(ValueError) as excinfo: client = AsyncDstackClient() - await client.get_quote('0' * 65) - assert '64 bytes' in str(excinfo.value) + await client.get_quote("0" * 65) + assert "64 bytes" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: client = AsyncDstackClient() - await client.get_quote(b'0' * 129) - assert '64 bytes' in str(excinfo.value) + await client.get_quote(b"0" * 129) + assert "64 bytes" in str(excinfo.value) + @pytest.mark.asyncio async def test_report_data(): - reportdata = 'test' + reportdata = "test" client = AsyncDstackClient() result = await client.get_quote(reportdata) tdxQuote = TdxQuote(result.decode_quote()) - reportdata = reportdata.encode('utf-8') + b'\x00' * (64 - len(reportdata)) + reportdata = reportdata.encode("utf-8") + b"\x00" * (64 - len(reportdata)) assert reportdata == tdxQuote.body.reportdata + + +def test_sync_client_is_reachable(): + """Test that sync client can check if service is reachable.""" + client = DstackClient() + is_reachable = client.is_reachable() + assert isinstance(is_reachable, bool) + + +@pytest.mark.asyncio +async def test_async_client_is_reachable(): + """Test that async client can check if service is reachable.""" + client = AsyncDstackClient() + is_reachable = await client.is_reachable() + assert isinstance(is_reachable, bool) + + +def test_tls_key_as_uint8array(): + """Test that TLS key can be converted to bytes with as_uint8array method.""" + client = DstackClient() + result = client.get_tls_key() + + # Test full length + full_bytes = result.as_uint8array() + assert isinstance(full_bytes, bytes) + assert len(full_bytes) > 0 + + # Test with max_length + key_32 = result.as_uint8array(32) + assert isinstance(key_32, bytes) + assert len(key_32) == 32 + assert len(key_32) != len(full_bytes) + + +def test_tls_key_with_alt_names(): + """Test TLS key generation with alt names.""" + client = DstackClient() + alt_names = ["localhost", "127.0.0.1"] + result = client.get_tls_key( + subject="test-subject", + alt_names=alt_names, + usage_ra_tls=True, + usage_server_auth=True, + usage_client_auth=True, + ) + assert isinstance(result, GetTlsKeyResponse) + assert result.key is not None + assert len(result.certificate_chain) > 0 + + +def test_unix_socket_file_not_exist(): + """Test that client raises error when Unix socket file doesn't exist.""" + # Temporarily remove environment variable to test file check + import os + + saved_env = os.environ.get("DSTACK_SIMULATOR_ENDPOINT") + if "DSTACK_SIMULATOR_ENDPOINT" in os.environ: + del os.environ["DSTACK_SIMULATOR_ENDPOINT"] + + try: + with pytest.raises(FileNotFoundError) as exc_info: + DstackClient("/non/existent/socket") + assert "Unix socket file /non/existent/socket does not exist" in str( + exc_info.value + ) + finally: + # Restore environment variable + if saved_env: + os.environ["DSTACK_SIMULATOR_ENDPOINT"] = saved_env + + +def test_non_unix_socket_endpoints(): + """Test that client doesn't throw error for non-unix socket paths.""" + import os + + saved_env = os.environ.get("DSTACK_SIMULATOR_ENDPOINT") + if "DSTACK_SIMULATOR_ENDPOINT" in os.environ: + del os.environ["DSTACK_SIMULATOR_ENDPOINT"] + + try: + # These should not raise errors + client1 = DstackClient("http://localhost:8080") + client2 = DstackClient("https://example.com") + assert client1 is not None + assert client2 is not None + finally: + # Restore environment variable + if saved_env: + os.environ["DSTACK_SIMULATOR_ENDPOINT"] = saved_env + + +@pytest.mark.asyncio +async def test_emit_event(): + """Test emit event functionality.""" + client = AsyncDstackClient() + # This should not raise an error + await client.emit_event("test-event", "test payload") + await client.emit_event("test-event-bytes", b"test payload bytes") + + +def test_sync_emit_event(): + """Test sync emit event functionality.""" + client = DstackClient() + # This should not raise an error + client.emit_event("test-event", "test payload") + client.emit_event("test-event-bytes", b"test payload bytes") + + +def test_emit_event_validation(): + """Test emit event input validation.""" + client = DstackClient() + + # Empty event name should raise error + with pytest.raises(ValueError) as exc_info: + client.emit_event("", "payload") + assert "event name cannot be empty" in str(exc_info.value) + + +# Test deprecated TappdClient +def test_tappd_client_deprecated(): + """Test that TappdClient shows deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + TappdClient() + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "TappdClient is deprecated" in str(w[0].message) + + +def test_tappd_client_derive_key_deprecated(): + """Test that TappdClient.derive_key shows deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + client = TappdClient() + + try: + client.derive_key("/", "test") + # Should have warnings for both constructor and derive_key + warning_messages = [str(warning.message) for warning in w] + assert any("TappdClient is deprecated" in msg for msg in warning_messages) + assert any("derive_key is deprecated" in msg for msg in warning_messages) + except Exception: + # It's OK if this fails due to simulator not supporting the old endpoint + pass + + +def test_tappd_client_tdx_quote_deprecated(): + """Test that TappdClient.tdx_quote shows deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + client = TappdClient() + + try: + client.tdx_quote("test data", "raw") + # Should have warnings for both constructor and tdx_quote + warning_messages = [str(warning.message) for warning in w] + assert any("TappdClient is deprecated" in msg for msg in warning_messages) + assert any("tdx_quote is deprecated" in msg for msg in warning_messages) + except Exception: + # It's OK if this fails due to simulator not supporting the old endpoint + pass diff --git a/sdk/python/tests/test_encrypt_env_vars.py b/sdk/python/tests/test_encrypt_env_vars.py new file mode 100644 index 000000000..cb662e7ac --- /dev/null +++ b/sdk/python/tests/test_encrypt_env_vars.py @@ -0,0 +1,110 @@ +import pytest + +from dstack_sdk.encrypt_env_vars import EnvVar +from dstack_sdk.encrypt_env_vars import encrypt_env_vars +from dstack_sdk.encrypt_env_vars import encrypt_env_vars_sync + + +def test_encrypt_env_vars_sync(): + """Test synchronous environment variable encryption.""" + # Test data + envs = [ + EnvVar(key="NODE_ENV", value="production"), + EnvVar(key="API_KEY", value="secret123"), + EnvVar(key="DATABASE_URL", value="postgres://localhost:5432/mydb"), + ] + + # Mock public key (32 bytes hex) + public_key_hex = "deadbeef" * 8 + + # Test encryption + encrypted_hex = encrypt_env_vars_sync(envs, public_key_hex) + + # Verify output + assert isinstance(encrypted_hex, str) + assert len(encrypted_hex) > 0 + assert len(encrypted_hex) % 2 == 0 # Should be valid hex + + # Should be able to decode as hex + encrypted_bytes = bytes.fromhex(encrypted_hex) + + # Should contain: 32 bytes public key + 12 bytes IV + encrypted data + assert len(encrypted_bytes) >= 44 # At least 32 + 12 bytes + + +@pytest.mark.asyncio +async def test_encrypt_env_vars_async(): + """Test asynchronous environment variable encryption.""" + # Test data + envs = [ + EnvVar(key="NODE_ENV", value="production"), + EnvVar(key="API_KEY", value="secret123"), + ] + + # Mock public key (32 bytes hex) + public_key_hex = "0x" + "deadbeef" * 8 + + # Test encryption + encrypted_hex = await encrypt_env_vars(envs, public_key_hex) + + # Verify output + assert isinstance(encrypted_hex, str) + assert len(encrypted_hex) > 0 + assert len(encrypted_hex) % 2 == 0 # Should be valid hex + + +def test_encrypt_env_vars_empty(): + """Test encryption with empty environment variables.""" + envs = [] + public_key_hex = "deadbeef" * 8 + + encrypted_hex = encrypt_env_vars_sync(envs, public_key_hex) + assert isinstance(encrypted_hex, str) + assert len(encrypted_hex) > 0 + + +def test_encrypt_env_vars_single(): + """Test encryption with single environment variable.""" + envs = [EnvVar(key="TEST", value="value")] + public_key_hex = "deadbeef" * 8 + + encrypted_hex = encrypt_env_vars_sync(envs, public_key_hex) + assert isinstance(encrypted_hex, str) + assert len(encrypted_hex) > 0 + + +def test_encrypt_env_vars_different_keys(): + """Test that different public keys produce different results.""" + envs = [EnvVar(key="TEST", value="value")] + + public_key1 = "deadbeef" * 8 + public_key2 = "abcdef01" * 8 + + encrypted1 = encrypt_env_vars_sync(envs, public_key1) + encrypted2 = encrypt_env_vars_sync(envs, public_key2) + + # Should be different due to different public keys + assert encrypted1 != encrypted2 + + +def test_env_var_namedtuple(): + """Test EnvVar namedtuple properties.""" + env = EnvVar(key="TEST_KEY", value="test_value") + + assert env.key == "TEST_KEY" + assert env.value == "test_value" + assert isinstance(env, tuple) + + +def test_encrypt_env_vars_unicode(): + """Test encryption with Unicode values.""" + envs = [ + EnvVar(key="MESSAGE", value="Hello 世界 🌍"), + EnvVar(key="EMOJI", value="🚀🎉💻"), + ] + + public_key_hex = "deadbeef" * 8 + encrypted_hex = encrypt_env_vars_sync(envs, public_key_hex) + + assert isinstance(encrypted_hex, str) + assert len(encrypted_hex) > 0 diff --git a/sdk/python/tests/test_ethereum.py b/sdk/python/tests/test_ethereum.py index 14ac1d3a3..35ab4a463 100644 --- a/sdk/python/tests/test_ethereum.py +++ b/sdk/python/tests/test_ethereum.py @@ -5,13 +5,72 @@ import pytest from eth_account.signers.local import LocalAccount -from dstack_sdk import AsyncDstackClient, GetKeyResponse +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient +from dstack_sdk import GetKeyResponse from dstack_sdk.ethereum import to_account +from dstack_sdk.ethereum import to_account_secure + @pytest.mark.asyncio -async def test_async_to_keypair(): +async def test_async_to_account(): client = AsyncDstackClient() - result = await client.get_key('test') + result = await client.get_key("test") + assert isinstance(result, GetKeyResponse) + account = to_account(result) + assert isinstance(account, LocalAccount) + + +def test_sync_to_account(): + client = DstackClient() + result = client.get_key("test") assert isinstance(result, GetKeyResponse) account = to_account(result) - assert isinstance(account, LocalAccount) \ No newline at end of file + assert isinstance(account, LocalAccount) + + +@pytest.mark.asyncio +async def test_async_to_account_secure(): + client = AsyncDstackClient() + result = await client.get_key("test") + assert isinstance(result, GetKeyResponse) + account = to_account_secure(result) + assert isinstance(account, LocalAccount) + + +def test_sync_to_account_secure(): + client = DstackClient() + result = client.get_key("test") + assert isinstance(result, GetKeyResponse) + account = to_account_secure(result) + assert isinstance(account, LocalAccount) + + +def test_to_account_with_tls_key(): + """Test to_account with TLS key response (should show warning).""" + client = DstackClient() + result = client.get_tls_key() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + account = to_account(result) + + assert isinstance(account, LocalAccount) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "Please don't use getTlsKey method" in str(w[0].message) + + +def test_to_account_secure_with_tls_key(): + """Test to_account_secure with TLS key response (should show warning).""" + client = DstackClient() + result = client.get_tls_key() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + account = to_account_secure(result) + + assert isinstance(account, LocalAccount) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "Please don't use getTlsKey method" in str(w[0].message) diff --git a/sdk/python/tests/test_get_compose_hash.py b/sdk/python/tests/test_get_compose_hash.py new file mode 100644 index 000000000..4a4559260 --- /dev/null +++ b/sdk/python/tests/test_get_compose_hash.py @@ -0,0 +1,242 @@ +from dstack_sdk.get_compose_hash import AppCompose +from dstack_sdk.get_compose_hash import DockerConfig +from dstack_sdk.get_compose_hash import get_compose_hash +from dstack_sdk.get_compose_hash import sort_object + + +def test_deterministic_json_key_sorting(): + """Test that object keys are sorted lexicographically.""" + compose1 = AppCompose( + runner="docker-compose", + docker_compose_file="docker-compose.yml", + bash_script="start.sh", + ) + + compose2 = AppCompose( + bash_script="start.sh", + docker_compose_file="docker-compose.yml", + runner="docker-compose", + ) + + # Both should produce the same hash despite different key order + assert get_compose_hash(compose1) == get_compose_hash(compose2) + + +def test_nested_object_key_sorting(): + """Test that nested object keys are sorted.""" + config1 = DockerConfig(registry="docker.io", username="user", token_key="token") + + config2 = DockerConfig(token_key="token", username="user", registry="docker.io") + + compose1 = AppCompose(runner="docker-compose", docker_config=config1) + + compose2 = AppCompose(runner="docker-compose", docker_config=config2) + + assert get_compose_hash(compose1) == get_compose_hash(compose2) + + +def test_array_order_preservation(): + """Test that array order is preserved.""" + compose1 = AppCompose( + runner="docker-compose", allowed_envs=["NODE_ENV", "PORT", "DB_URL"] + ) + + compose2 = AppCompose( + runner="docker-compose", allowed_envs=["PORT", "DB_URL", "NODE_ENV"] + ) + + # Different array orders should produce different hashes + assert get_compose_hash(compose1) != get_compose_hash(compose2) + + +def test_preprocessing_bash_runner(): + """Test preprocessing removes docker_compose_file when runner is bash.""" + compose = AppCompose( + runner="bash", bash_script="start.sh", docker_compose_file="docker-compose.yml" + ) + + hash_normalized = get_compose_hash(compose, normalize=True) + + compose2 = AppCompose(runner="bash", bash_script="start.sh") + + assert hash_normalized == get_compose_hash(compose2, normalize=True) + + +def test_preprocessing_docker_compose_runner(): + """Test preprocessing removes bash_script when runner is docker-compose.""" + compose = AppCompose( + runner="docker-compose", + docker_compose_file="docker-compose.yml", + bash_script="start.sh", + ) + + hash_normalized = get_compose_hash(compose, normalize=True) + + compose2 = AppCompose( + runner="docker-compose", docker_compose_file="docker-compose.yml" + ) + + assert hash_normalized == get_compose_hash(compose2, normalize=True) + + +def test_preprocessing_empty_pre_launch_script(): + """Test preprocessing removes empty pre_launch_script.""" + compose1 = AppCompose( + runner="docker-compose", + docker_compose_file="docker-compose.yml", + pre_launch_script="", + ) + + compose2 = AppCompose( + runner="docker-compose", docker_compose_file="docker-compose.yml" + ) + + assert get_compose_hash(compose1, normalize=True) == get_compose_hash( + compose2, normalize=True + ) + + +def test_special_value_handling(): + """Test that NaN and Infinity are converted to null.""" + data_with_nan = {"runner": "docker-compose", "special_value": float("nan")} + + data_with_null = {"runner": "docker-compose", "special_value": None} + + compose1 = AppCompose.from_dict(data_with_nan) + compose2 = AppCompose.from_dict(data_with_null) + + # Should produce same hash since NaN becomes null + assert get_compose_hash(compose1) == get_compose_hash(compose2) + + +def test_cross_language_compatibility(): + """Test deterministic hash for reference data.""" + compose = AppCompose( + runner="docker-compose", + docker_compose_file="docker-compose.yml", + manifest_version=1, + name="test-app", + public_logs=True, + kms_enabled=False, + allowed_envs=["NODE_ENV"], + ) + + hash_result = get_compose_hash(compose) + + # Should be a deterministic 64-character hex hash + assert len(hash_result) == 64 + assert all(c in "0123456789abcdef" for c in hash_result) + + # Should be consistent across runs + assert hash_result == get_compose_hash(compose) + + +def test_utf8_support(): + """Test handling of non-ASCII characters.""" + compose1 = AppCompose( + runner="docker-compose", name="测试应用", bash_script="echo '🚀 Deploy'" + ) + + compose2 = AppCompose( + bash_script="echo '🚀 Deploy'", runner="docker-compose", name="测试应用" + ) + + # Should be same despite key order + assert get_compose_hash(compose1) == get_compose_hash(compose2) + + +def test_empty_compose(): + """Test handling of minimal compose.""" + compose = AppCompose(runner="bash") + hash_result = get_compose_hash(compose) + + assert len(hash_result) == 64 + assert isinstance(hash_result, str) + + +def test_boolean_values(): + """Test handling of boolean values.""" + compose1 = AppCompose( + runner="docker-compose", public_logs=True, kms_enabled=False, secure_time=True + ) + + compose2 = AppCompose( + secure_time=True, public_logs=True, runner="docker-compose", kms_enabled=False + ) + + assert get_compose_hash(compose1) == get_compose_hash(compose2) + + +def test_normalize_parameter(): + """Test that normalize parameter controls preprocessing.""" + compose = AppCompose( + runner="bash", bash_script="start.sh", docker_compose_file="docker-compose.yml" + ) + + hash_without_normalize = get_compose_hash(compose, normalize=False) + hash_with_normalize = get_compose_hash(compose, normalize=True) + + # Should be different because preprocessing is only applied with normalize=True + assert hash_without_normalize != hash_with_normalize + + +def test_docker_config(): + """Test DockerConfig handling.""" + config = DockerConfig( + registry="docker.io", username="testuser", token_key="secret123" + ) + + compose = AppCompose(runner="docker-compose", docker_config=config) + + hash_result = get_compose_hash(compose) + assert isinstance(hash_result, str) + assert len(hash_result) == 64 + + +def test_key_provider_fields(): + """Test key provider related fields.""" + compose = AppCompose( + runner="docker-compose", + key_provider="kms", + key_provider_id="abcd1234", + local_key_provider_enabled=True, + ) + + hash_result = get_compose_hash(compose) + assert isinstance(hash_result, str) + assert len(hash_result) == 64 + + +def test_from_dict_conversion(): + """Test AppCompose.from_dict functionality.""" + data = { + "runner": "docker-compose", + "manifest_version": 1, + "name": "test-app", + "public_logs": True, + "docker_config": {"registry": "docker.io", "username": "user"}, + } + + compose = AppCompose.from_dict(data) + + assert compose.runner == "docker-compose" + assert compose.manifest_version == 1 + assert compose.name == "test-app" + assert compose.public_logs is True + assert isinstance(compose.docker_config, DockerConfig) + assert compose.docker_config.registry == "docker.io" + + +def test_sort_object_function(): + """Test the sort_object utility function.""" + obj = {"gamma": 3, "alpha": 1, "beta": 2, "nested": {"z": 26, "a": 1}} + + sorted_obj = sort_object(obj) + keys = list(sorted_obj.keys()) + + # Keys should be sorted + assert keys == ["alpha", "beta", "gamma", "nested"] + + # Nested keys should also be sorted + nested_keys = list(sorted_obj["nested"].keys()) + assert nested_keys == ["a", "z"] diff --git a/sdk/python/tests/test_solana.py b/sdk/python/tests/test_solana.py index b8a7c3e8e..6e42794e8 100644 --- a/sdk/python/tests/test_solana.py +++ b/sdk/python/tests/test_solana.py @@ -5,13 +5,72 @@ import pytest from solders.keypair import Keypair -from dstack_sdk import AsyncDstackClient, GetKeyResponse +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient +from dstack_sdk import GetKeyResponse from dstack_sdk.solana import to_keypair +from dstack_sdk.solana import to_keypair_secure + @pytest.mark.asyncio async def test_async_to_keypair(): client = AsyncDstackClient() - result = await client.get_key('test') + result = await client.get_key("test") + assert isinstance(result, GetKeyResponse) + keypair = to_keypair(result) + assert isinstance(keypair, Keypair) + + +def test_sync_to_keypair(): + client = DstackClient() + result = client.get_key("test") assert isinstance(result, GetKeyResponse) keypair = to_keypair(result) - assert isinstance(keypair, Keypair) \ No newline at end of file + assert isinstance(keypair, Keypair) + + +@pytest.mark.asyncio +async def test_async_to_keypair_secure(): + client = AsyncDstackClient() + result = await client.get_key("test") + assert isinstance(result, GetKeyResponse) + keypair = to_keypair_secure(result) + assert isinstance(keypair, Keypair) + + +def test_sync_to_keypair_secure(): + client = DstackClient() + result = client.get_key("test") + assert isinstance(result, GetKeyResponse) + keypair = to_keypair_secure(result) + assert isinstance(keypair, Keypair) + + +def test_to_keypair_with_tls_key(): + """Test to_keypair with TLS key response (should show warning).""" + client = DstackClient() + result = client.get_tls_key() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + keypair = to_keypair(result) + + assert isinstance(keypair, Keypair) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "Please don't use getTlsKey method" in str(w[0].message) + + +def test_to_keypair_secure_with_tls_key(): + """Test to_keypair_secure with TLS key response (should show warning).""" + client = DstackClient() + result = client.get_tls_key() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + keypair = to_keypair_secure(result) + + assert isinstance(keypair, Keypair) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "Please don't use getTlsKey method" in str(w[0].message) diff --git a/sdk/python/tests/test_verify_env_encrypt_public_key.py b/sdk/python/tests/test_verify_env_encrypt_public_key.py new file mode 100644 index 000000000..78c9c0f57 --- /dev/null +++ b/sdk/python/tests/test_verify_env_encrypt_public_key.py @@ -0,0 +1,168 @@ +from dstack_sdk.verify_env_encrypt_public_key import verify_env_encrypt_public_key +from dstack_sdk.verify_env_encrypt_public_key import verify_signature_simple + + +def test_verify_signature_simple(): + """Test simple signature verification without recovery.""" + # Test data (32 bytes public key) + public_key = bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ) + + # Test signature (65 bytes with recovery ID) + signature = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ) + + app_id = "00" * 20 + + # Should process without error + result = verify_signature_simple(public_key, signature, app_id) + assert isinstance(result, bool) + + +def test_verify_signature_simple_with_0x_prefix(): + """Test signature verification with 0x prefix in app_id.""" + public_key = bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ) + signature = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ) + + app_id_without_prefix = "00" * 20 + app_id_with_prefix = "0x" + "00" * 20 + + result1 = verify_signature_simple(public_key, signature, app_id_without_prefix) + result2 = verify_signature_simple(public_key, signature, app_id_with_prefix) + + # Should handle both formats the same way + assert result1 == result2 + + +def test_verify_signature_invalid_signature_length(): + """Test that invalid signature length returns False/None.""" + public_key = bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ) + + # Invalid signature length (should be 65 bytes) + invalid_signature_short = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed" + ) + invalid_signature_long = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef0012" + ) + + app_id = "00" * 20 + + # Should return False for invalid lengths + assert verify_signature_simple(public_key, invalid_signature_short, app_id) is False + assert verify_signature_simple(public_key, invalid_signature_long, app_id) is False + + # Public key verification should return None for invalid lengths + assert ( + verify_env_encrypt_public_key(public_key, invalid_signature_short, app_id) + is None + ) + assert ( + verify_env_encrypt_public_key(public_key, invalid_signature_long, app_id) + is None + ) + + +def test_verify_env_encrypt_public_key(): + """Test public key recovery (simplified implementation).""" + # Test data + public_key = bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ) + signature = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ) + app_id = "00" * 20 + + # Note: Our implementation is simplified and may return None + # This is expected until full ECDSA recovery is implemented + result = verify_env_encrypt_public_key(public_key, signature, app_id) + assert result is None or (isinstance(result, str) and result.startswith("0x")) + + +def test_message_construction(): + """Test that the message is constructed correctly.""" + public_key = b"\x01\x02\x03\x04" * 8 # 32 bytes + signature = b"\x00" * 65 # 65 bytes + app_id = "deadbeef" + + # This should process the message correctly even if signature verification fails + result = verify_signature_simple(public_key, signature, app_id) + assert isinstance(result, bool) + + +def test_unicode_handling(): + """Test handling of app_id with different formats.""" + public_key = bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ) + signature = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ) + + # Test various app_id formats + app_ids = ["deadbeef", "0xdeadbeef", "DEADBEEF", "0xDEADBEEF", "1234abcd"] + + for app_id in app_ids: + result = verify_signature_simple(public_key, signature, app_id) + assert isinstance(result, bool) + + +def test_empty_public_key(): + """Test handling of edge cases.""" + signature = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ) + app_id = "deadbeef" + + # Test with different public key lengths + empty_key = b"" + short_key = b"\x01\x02" + normal_key = b"\x01" * 32 + long_key = b"\x01" * 64 + + for key in [empty_key, short_key, normal_key, long_key]: + # Should not crash, should return boolean or None + result_simple = verify_signature_simple(key, signature, app_id) + result_verify = verify_env_encrypt_public_key(key, signature, app_id) + + assert isinstance(result_simple, bool) + assert result_verify is None or isinstance(result_verify, str) + + +def test_malformed_app_id(): + """Test handling of malformed app IDs.""" + public_key = bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ) + signature = bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ) + + # Test with malformed hex strings + malformed_app_ids = [ + "xyz", # Invalid hex + "0xzyz", # Invalid hex with prefix + "gg", # Invalid hex + "", # Empty + ] + + for app_id in malformed_app_ids: + # Should handle gracefully and return False/None + try: + result_simple = verify_signature_simple(public_key, signature, app_id) + result_verify = verify_env_encrypt_public_key(public_key, signature, app_id) + + assert isinstance(result_simple, bool) + assert result_verify is None or isinstance(result_verify, str) + except ValueError: + # It's acceptable to raise ValueError for invalid hex + pass From cba76908eb86df196b2882b45c173904d355402d Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Thu, 14 Aug 2025 21:13:47 +0800 Subject: [PATCH 02/20] imp(sdk/python): test cases for AsyncTappdClient & added version header --- sdk/python/src/dstack_sdk/dstack_client.py | 27 ++++++++-- sdk/python/tests/test_client.py | 60 ++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index fad539138..18642c74b 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -18,6 +18,9 @@ logger = logging.getLogger("dstack_sdk") +# SDK version for User-Agent header +__version__ = "0.2.0" + INIT_MR = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" @@ -216,7 +219,12 @@ def _send_rpc_request(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any """Send an RPC request and return the parsed JSON response.""" with httpx.Client(transport=self.transport, base_url=self.base_url) as client: response = client.post( - path, json=payload, headers={"Content-Type": "application/json"} + path, + json=payload, + headers={ + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + }, ) response.raise_for_status() from typing import cast @@ -298,7 +306,10 @@ def is_reachable(self) -> bool: response = client.post( "/prpc/Tappd.Info", json={}, - headers={"Content-Type": "application/json"}, + headers={ + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + }, ) response.raise_for_status() return True @@ -330,7 +341,12 @@ async def _send_rpc_request( transport=self.transport, base_url=self.base_url ) as client: response = await client.post( - path, json=payload, headers={"Content-Type": "application/json"} + path, + json=payload, + headers={ + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + }, ) response.raise_for_status() from typing import cast @@ -414,7 +430,10 @@ async def is_reachable(self) -> bool: response = await client.post( "/prpc/Tappd.Info", json={}, - headers={"Content-Type": "application/json"}, + headers={ + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + }, ) response.raise_for_status() return True diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 89393f8f7..2b667d8d8 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -6,6 +6,7 @@ from evidence_api.tdx.quote import TdxQuote from dstack_sdk import AsyncDstackClient +from dstack_sdk import AsyncTappdClient from dstack_sdk import DstackClient from dstack_sdk import GetKeyResponse from dstack_sdk import GetQuoteResponse @@ -295,3 +296,62 @@ def test_tappd_client_tdx_quote_deprecated(): except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass + + +# Test AsyncTappdClient +def test_async_tappd_client_deprecated(): + """Test that AsyncTappdClient shows deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + AsyncTappdClient() + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "AsyncTappdClient is deprecated" in str(w[0].message) + + +@pytest.mark.asyncio +async def test_async_tappd_client_derive_key_deprecated(): + """Test that AsyncTappdClient.derive_key shows deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + client = AsyncTappdClient() + + try: + await client.derive_key("/", "test") + # Should have warnings for both constructor and derive_key + warning_messages = [str(warning.message) for warning in w] + assert any( + "AsyncTappdClient is deprecated" in msg for msg in warning_messages + ) + assert any("derive_key is deprecated" in msg for msg in warning_messages) + except Exception: + # It's OK if this fails due to simulator not supporting the old endpoint + pass + + +@pytest.mark.asyncio +async def test_async_tappd_client_tdx_quote_deprecated(): + """Test that AsyncTappdClient.tdx_quote shows deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + client = AsyncTappdClient() + + try: + await client.tdx_quote("test data", "raw") + # Should have warnings for both constructor and tdx_quote + warning_messages = [str(warning.message) for warning in w] + assert any( + "AsyncTappdClient is deprecated" in msg for msg in warning_messages + ) + assert any("tdx_quote is deprecated" in msg for msg in warning_messages) + except Exception: + # It's OK if this fails due to simulator not supporting the old endpoint + pass + + +@pytest.mark.asyncio +async def test_async_tappd_client_is_reachable(): + """Test that AsyncTappdClient can check if service is reachable.""" + client = AsyncTappdClient() + is_reachable = await client.is_reachable() + assert isinstance(is_reachable, bool) From 46399c2640c1b7609c64e566c8caf4704374b835 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Thu, 14 Aug 2025 21:22:16 +0800 Subject: [PATCH 03/20] chore(sdk): added test output scripts to compare implementations across different languages. --- sdk/js/test-outputs.js | 155 ++++++++++++++++++++++++++++++ sdk/python/Makefile | 53 +++++++++++ sdk/python/pyproject.toml | 6 +- sdk/python/test_outputs.py | 188 +++++++++++++++++++++++++++++++++++++ 4 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 sdk/js/test-outputs.js create mode 100644 sdk/python/Makefile create mode 100644 sdk/python/test_outputs.py diff --git a/sdk/js/test-outputs.js b/sdk/js/test-outputs.js new file mode 100644 index 000000000..5d22878ae --- /dev/null +++ b/sdk/js/test-outputs.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +const { DstackClient, TappdClient, getComposeHash, verifyEnvEncryptPublicKey } = require('./dist/node/index.js'); +const { toViemAccount, toViemAccountSecure } = require('./dist/node/viem.js'); +const { toKeypair, toKeypairSecure } = require('./dist/node/solana.js'); + +async function main() { + console.log("=== JS SDK Output Test ==="); + + try { + // Test client get_key + const client = new DstackClient(); + console.log("\n1. Testing DstackClient.getKey()"); + + const testPaths = [ + { path: "test/wallet", purpose: "ethereum" }, + { path: "test/signing", purpose: "solana" }, + { path: "user/alice", purpose: "mainnet" } + ]; + + for (const { path, purpose } of testPaths) { + const keyResult = await client.getKey(path, purpose); + console.log(`getKey('${path}', '${purpose}'):`); + console.log(` key: ${Buffer.from(keyResult.key).toString('hex')}`); + console.log(` signature_chain length: ${keyResult.signature_chain.length}`); + console.log(` signature_chain[0]: ${Buffer.from(keyResult.signature_chain[0]).toString('hex')}`); + } + + // Test viem integration + console.log("\n2. Testing Viem Integration"); + const ethKey = await client.getKey("eth/test", "wallet"); + + console.log("\n2.1 toViemAccount (legacy):"); + try { + const account = toViemAccount(ethKey); + console.log(` address: ${account.address}`); + console.log(` type: ${account.type}`); + } catch (error) { + console.log(` error: ${error.message}`); + } + + console.log("\n2.2 toViemAccountSecure:"); + try { + const accountSecure = toViemAccountSecure(ethKey); + console.log(` address: ${accountSecure.address}`); + console.log(` type: ${accountSecure.type}`); + } catch (error) { + console.log(` error: ${error.message}`); + } + + // Test solana integration + console.log("\n3. Testing Solana Integration"); + const solKey = await client.getKey("sol/test", "wallet"); + + console.log("\n3.1 toKeypair (legacy):"); + try { + const keypair = toKeypair(solKey); + console.log(` publicKey: ${keypair.publicKey.toString()}`); + console.log(` secretKey length: ${keypair.secretKey.length}`); + console.log(` secretKey (first 32 bytes): ${Buffer.from(keypair.secretKey.slice(0, 32)).toString('hex')}`); + } catch (error) { + console.log(` error: ${error.message}`); + } + + console.log("\n3.2 toKeypairSecure:"); + try { + const keypairSecure = toKeypairSecure(solKey); + console.log(` publicKey: ${keypairSecure.publicKey.toString()}`); + console.log(` secretKey length: ${keypairSecure.secretKey.length}`); + console.log(` secretKey (first 32 bytes): ${Buffer.from(keypairSecure.secretKey.slice(0, 32)).toString('hex')}`); + } catch (error) { + console.log(` error: ${error.message}`); + } + + // Test TappdClient (deprecated) + console.log("\n4. Testing TappdClient (deprecated)"); + try { + const tappdClient = new TappdClient(); + console.log("\n4.1 TappdClient.getKey():"); + const tappdKey = await tappdClient.getKey("test/wallet", "ethereum"); + console.log(` key: ${Buffer.from(tappdKey.key).toString('hex')}`); + console.log(` signature_chain length: ${tappdKey.signature_chain.length}`); + + console.log("\n4.2 TappdClient.tdxQuote():"); + const tappdQuote = await tappdClient.tdxQuote("test-data", "raw"); + console.log(` quote length: ${tappdQuote.quote.length}`); + console.log(` event_log length: ${tappdQuote.event_log.length}`); + console.log(` rtmrs count: ${tappdQuote.replayRtmrs().length}`); + } catch (error) { + console.log(` error: ${error.message}`); + } + + // Test quotes + console.log("\n5. Testing Quote Methods"); + console.log("\n5.1 DstackClient.getQuote():"); + const dstackQuote = await client.getQuote("test-data-for-quote"); + console.log(` quote length: ${dstackQuote.quote.length}`); + console.log(` event_log length: ${dstackQuote.event_log.length}`); + console.log(` rtmrs count: ${dstackQuote.replayRtmrs().length}`); + + // Test getComposeHash + console.log("\n6. Testing getComposeHash"); + const testComposes = [ + { + manifest_version: 1, + name: "test-app", + runner: "docker-compose", + docker_compose_file: "services:\\n app:\\n image: test\\n ports:\\n - 8080:8080" + }, + { + manifest_version: 1, + name: "another-app", + runner: "docker-compose", + docker_compose_file: "services:\\n web:\\n build: .\\n environment:\\n - NODE_ENV=production" + } + ]; + + testComposes.forEach((compose, index) => { + const hash = getComposeHash(compose); + console.log(`compose ${index + 1}: ${hash}`); + console.log(` name: ${compose.name}`); + console.log(` runner: ${compose.runner}`); + }); + + // Test verifyEnvEncryptPublicKey + console.log("\n7. Testing verifyEnvEncryptPublicKey"); + const testCases = [ + { + publicKey: Buffer.from('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a', 'hex'), + signature: Buffer.from('8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00', 'hex'), + appId: '0000000000000000000000000000000000000000' + }, + { + publicKey: Buffer.from('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', 'hex'), + signature: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), + appId: 'invalid-app-id' + } + ]; + + testCases.forEach((testCase, index) => { + try { + const result = verifyEnvEncryptPublicKey(testCase.publicKey, testCase.signature, testCase.appId); + console.log(`test case ${index + 1}: ${result ? Buffer.from(result).toString('hex') : 'null'}`); + } catch (error) { + console.log(`test case ${index + 1}: error - ${error.message}`); + } + }); + + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/sdk/python/Makefile b/sdk/python/Makefile new file mode 100644 index 000000000..3d13ec35b --- /dev/null +++ b/sdk/python/Makefile @@ -0,0 +1,53 @@ +.PHONY: install lint format type-check test check-all verify + +# Install dependencies including linting tools +install: + pdm install -G lint + +# Run all formatters (using PDM scripts) +format: + pdm run fmt + +# Alias for format +fmt: format + +# Run linting tools (using PDM scripts) +lint: + pdm run lint + +# Run type checking only +type-check: + pdm run mypy src/ + +# Run tests +test: + @if [ -z "$$DSTACK_SIMULATOR_ENDPOINT" ]; then \ + echo "error: DSTACK_SIMULATOR_ENDPOINT environment variable is not set"; \ + exit 1; \ + fi + @if [ -z "$$TAPPD_SIMULATOR_ENDPOINT" ]; then \ + echo "error: TAPPD_SIMULATOR_ENDPOINT environment variable is not set"; \ + exit 1; \ + fi + @if [[ "$$DSTACK_SIMULATOR_ENDPOINT" == /* ]] && [ ! -e "$$DSTACK_SIMULATOR_ENDPOINT" ]; then \ + echo "error: dstack socket file $$DSTACK_SIMULATOR_ENDPOINT does not exist"; \ + exit 1; \ + fi + @if [[ "$$TAPPD_SIMULATOR_ENDPOINT" == /* ]] && [ ! -e "$$TAPPD_SIMULATOR_ENDPOINT" ]; then \ + echo "error: tappd socket file $$TAPPD_SIMULATOR_ENDPOINT does not exist"; \ + exit 1; \ + fi + pdm run test + +# Run all checks (format, lint, skip test) +check-all: + pdm run fmt + pdm run lint + +# Check code without fixing (for CI) +check: + pdm run check + +# Run verification without formatting (skip test) +verify: + pdm run check diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 35e24bcbb..0293ec3fc 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -128,7 +128,7 @@ repository = "pypi" [tool.pdm.scripts] # Formatting scripts fmt = {composite = ["isort src/ tests/", "black src/ tests/"]} -format = {call = "pdm:fmt"} +format = {composite = ["isort src/ tests/", "black src/ tests/"]} # Linting scripts lint = {composite = ["flake8 src/ tests/", "mypy src/"]} @@ -139,8 +139,8 @@ test = "pytest" test-cov = "pytest --cov=src/" # Combined scripts -check-all = {composite = ["pdm:fmt", "pdm:lint", "pdm:test"]} -verify = {composite = ["pdm:check", "pdm:test"]} +check-all = {composite = ["fmt", "lint", "test"]} +verify = {composite = ["check", "test"]} [tool.pdm.dev-dependencies] solana = [ diff --git a/sdk/python/test_outputs.py b/sdk/python/test_outputs.py new file mode 100644 index 000000000..ad2074c6e --- /dev/null +++ b/sdk/python/test_outputs.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +import asyncio +import sys + +from dstack_sdk import DstackClient, AsyncDstackClient, TappdClient, AsyncTappdClient, get_compose_hash, verify_env_encrypt_public_key + + +async def main(): + print("=== Python SDK Output Test ===") + + try: + # Test client get_key + client = DstackClient() + print("\n1. Testing DstackClient.get_key()") + + test_paths = [ + {"path": "test/wallet", "purpose": "ethereum"}, + {"path": "test/signing", "purpose": "solana"}, + {"path": "user/alice", "purpose": "mainnet"} + ] + + for test_case in test_paths: + path, purpose = test_case["path"], test_case["purpose"] + key_result = client.get_key(path, purpose) + print(f"get_key('{path}', '{purpose}'):") + print(f" key: {key_result.decode_key().hex()}") + print(f" signature_chain length: {len(key_result.signature_chain)}") + print(f" signature_chain[0]: {key_result.decode_signature_chain()[0].hex()}") + + # Test viem integration (if available) + print("\n2. Testing Viem Integration") + eth_key = client.get_key("eth/test", "wallet") + + print("\n2.1 to_account (legacy):") + try: + from dstack_sdk.ethereum import to_account + account = to_account(eth_key) + print(f" address: {account.address}") + print(f" type: ethereum account") + except ImportError: + print(" error: Ethereum integration not available (install with pip install 'dstack-sdk[eth]')") + except Exception as error: + print(f" error: {error}") + + print("\n2.2 to_account_secure:") + try: + from dstack_sdk.ethereum import to_account_secure + account_secure = to_account_secure(eth_key) + print(f" address: {account_secure.address}") + print(f" type: ethereum account") + except ImportError: + print(" error: Ethereum integration not available (install with pip install 'dstack-sdk[eth]')") + except Exception as error: + print(f" error: {error}") + + # Test solana integration (if available) + print("\n3. Testing Solana Integration") + sol_key = client.get_key("sol/test", "wallet") + + print("\n3.1 to_keypair (legacy):") + try: + from dstack_sdk.solana import to_keypair + keypair = to_keypair(sol_key) + print(f" publicKey: {keypair.pubkey()}") + print(f" secretKey length: {len(bytes(keypair))}") + print(f" secretKey (first 32 bytes): {bytes(keypair)[:32].hex()}") + except ImportError: + print(" error: Solana integration not available (install with pip install 'dstack-sdk[sol]')") + except Exception as error: + print(f" error: {error}") + + print("\n3.2 to_keypair_secure:") + try: + from dstack_sdk.solana import to_keypair_secure + keypair_secure = to_keypair_secure(sol_key) + print(f" publicKey: {keypair_secure.pubkey()}") + print(f" secretKey length: {len(bytes(keypair_secure))}") + print(f" secretKey (first 32 bytes): {bytes(keypair_secure)[:32].hex()}") + except ImportError: + print(" error: Solana integration not available (install with pip install 'dstack-sdk[sol]')") + except Exception as error: + print(f" error: {error}") + + # Test TappdClient (deprecated) + print("\n4. Testing TappdClient (deprecated)") + try: + tappd_client = TappdClient() + print("\n4.1 TappdClient.get_key():") + tappd_key = tappd_client.get_key("test/wallet", "ethereum") + print(f" key: {tappd_key.decode_key().hex()}") + print(f" signature_chain length: {len(tappd_key.signature_chain)}") + + print("\n4.2 TappdClient.tdx_quote():") + tappd_quote = tappd_client.tdx_quote("test-data", "raw") + print(f" quote length: {len(tappd_quote.quote)}") + print(f" event_log length: {len(tappd_quote.event_log)}") + print(f" rtmrs count: {len(tappd_quote.replay_rtmrs())}") + except Exception as error: + print(f" error: {error}") + + # Test AsyncTappdClient (deprecated) + print("\n4.3 Testing AsyncTappdClient (deprecated)") + try: + async_tappd_client = AsyncTappdClient() + print("\n4.3.1 AsyncTappdClient.get_key():") + async_tappd_key = await async_tappd_client.get_key("test/wallet", "ethereum") + print(f" key: {async_tappd_key.decode_key().hex()}") + print(f" signature_chain length: {len(async_tappd_key.signature_chain)}") + + print("\n4.3.2 AsyncTappdClient.tdx_quote():") + async_tappd_quote = await async_tappd_client.tdx_quote("test-data", "raw") + print(f" quote length: {len(async_tappd_quote.quote)}") + print(f" event_log length: {len(async_tappd_quote.event_log)}") + print(f" rtmrs count: {len(async_tappd_quote.replay_rtmrs())}") + except Exception as error: + print(f" error: {error}") + + # Test quotes + print("\n5. Testing Quote Methods") + print("\n5.1 DstackClient.get_quote():") + dstack_quote = client.get_quote("test-data-for-quote") + print(f" quote length: {len(dstack_quote.quote)}") + print(f" event_log length: {len(dstack_quote.event_log)}") + print(f" rtmrs count: {len(dstack_quote.replay_rtmrs())}") + + print("\n5.2 AsyncDstackClient.get_quote():") + async_client = AsyncDstackClient() + async_dstack_quote = await async_client.get_quote("test-data-for-quote") + print(f" quote length: {len(async_dstack_quote.quote)}") + print(f" event_log length: {len(async_dstack_quote.event_log)}") + print(f" rtmrs count: {len(async_dstack_quote.replay_rtmrs())}") + + # Test get_compose_hash + print("\n6. Testing get_compose_hash") + test_composes = [ + { + "manifest_version": 1, + "name": "test-app", + "runner": "docker-compose", + "docker_compose_file": "services:\\n app:\\n image: test\\n ports:\\n - 8080:8080" + }, + { + "manifest_version": 1, + "name": "another-app", + "runner": "docker-compose", + "docker_compose_file": "services:\\n web:\\n build: .\\n environment:\\n - NODE_ENV=production" + } + ] + + for index, compose in enumerate(test_composes): + hash_value = get_compose_hash(compose) + print(f"compose {index + 1}: {hash_value}") + print(f" name: {compose['name']}") + print(f" runner: {compose['runner']}") + + # Test verify_env_encrypt_public_key + print("\n7. Testing verify_env_encrypt_public_key") + test_cases = [ + { + "public_key": bytes.fromhex('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a'), + "signature": bytes.fromhex('8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00'), + "app_id": '0000000000000000000000000000000000000000' + }, + { + "public_key": bytes.fromhex('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'), + "signature": bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), + "app_id": 'invalid-app-id' + } + ] + + for index, test_case in enumerate(test_cases): + try: + result = verify_env_encrypt_public_key( + test_case["public_key"], + test_case["signature"], + test_case["app_id"] + ) + print(f"test case {index + 1}: {result.hex() if result else 'null'}") + except Exception as error: + print(f"test case {index + 1}: error - {error}") + + except Exception as error: + print(f"Error: {error}") + sys.exit(1) + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file From 43102882828e11907da63f6ce78e47efc3f46586 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Thu, 14 Aug 2025 21:22:41 +0800 Subject: [PATCH 04/20] docs(sdk/python): add AI generated docs. --- sdk/python/README.md | 665 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 585 insertions(+), 80 deletions(-) diff --git a/sdk/python/README.md b/sdk/python/README.md index 8d8d7d45b..59241a09e 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -1,6 +1,6 @@ # dstack SDK -This SDK provides a Python client for communicating with the dstack Guest Agent, which is available inside dstack CVM. +The dstack SDK provides a Python client for secure communication with the dstack Trusted Execution Environment (TEE). This SDK enables applications to derive cryptographic keys, generate remote attestation quotes, and perform other security-critical operations within confidential computing environments. ## Installation @@ -8,177 +8,682 @@ This SDK provides a Python client for communicating with the dstack Guest Agent, pip install dstack-sdk ``` +## Overview + +The dstack SDK enables secure communication with dstack Trusted Execution Environment (TEE) instances. dstack applications are defined using `app-compose.json` (based on the `AppCompose` structure) and deployed as containerized applications using Docker Compose. + +### Application Architecture + +dstack applications consist of: +- **App Configuration**: `app-compose.json` defining app metadata, security settings, and Docker Compose content +- **Container Deployment**: Docker Compose configuration embedded within the app definition +- **TEE Integration**: Access to TEE functionality via Unix socket (`/var/run/dstack.sock`) + +### SDK Capabilities + +- **Key Derivation**: Deterministic secp256k1 key generation for blockchain and Web3 applications +- **Remote Attestation**: TDX quote generation providing cryptographic proof of execution environment +- **TLS Certificate Management**: Fresh certificate generation with optional RA-TLS support for secure connections +- **Deployment Security**: Client-side encryption of sensitive environment variables ensuring secrets are only accessible to target TEE applications +- **Blockchain Integration**: Ready-to-use adapters for Ethereum and Solana ecosystems + +### Socket Connection Requirements + +To use the SDK, your Docker Compose configuration must bind-mount the dstack socket: + +```yaml +# docker-compose.yml +services: + your-app: + image: your-app-image + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock # dstack OS 0.5.x + # For dstack OS 0.3.x compatibility (deprecated): + # - /var/run/tappd.sock:/var/run/tappd.sock +``` + ## Basic Usage +### Application Setup + +First, ensure your dstack application is properly configured: + +**1. App Configuration (`app-compose.json`)** +```json +{ + "manifest_version": 1, + "name": "my-secure-app", + "runner": "docker-compose", + "docker_compose_file": "services:\\n app:\\n build: .\\n volumes:\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n environment:\\n - NODE_ENV=production", + "public_tcbinfo": true, + "kms_enabled": false, + "gateway_enabled": false +} +``` + +**Note**: The `docker_compose_file` field contains the actual Docker Compose YAML content as a string, not a file path. + +### SDK Integration + ```python from dstack_sdk import DstackClient, AsyncDstackClient -# Synchronous client +# Create client - automatically connects to /var/run/dstack.sock client = DstackClient() -# Caution: You don't need to do this most of the time. -http_client = DstackClient('http://localhost:8000') +# For local development with simulator +dev_client = DstackClient('http://localhost:8090') -# Asynchronous client +# Async client async_client = AsyncDstackClient() -# Get the information of the Base Image. -info = client.info() # or await async_client.info() -print(info.app_id) # Application ID -print(info.tcb_info.mrtd) # Access TCB info directly -print(info.tcb_info.event_log[0].event) # Access event log entries +# Get TEE instance information +info = client.info() +print('App ID:', info.app_id) +print('Instance ID:', info.instance_id) +print('App Name:', info.app_name) +print('TCB Info:', info.tcb_info) + +# Derive deterministic keys for blockchain applications +wallet_key = client.get_key('wallet/ethereum', 'mainnet') +print('Derived key (32 bytes):', wallet_key.decode_key()) # secp256k1 private key bytes +print('Signature chain:', wallet_key.signature_chain) # Authenticity proof + +# Generate remote attestation quote +application_data = json.dumps({ + "version": "1.0.0", + "timestamp": time.time(), + "user_id": "alice" +}) + +quote = client.get_quote(application_data.encode()) +print('TDX Quote:', quote.quote) +print('Event Log:', quote.event_log) + +# Verify measurement registers +rtmrs = quote.replay_rtmrs() +print('RTMR0-3:', rtmrs) +``` -# Derive a key with optional path and subject -key_result = client.get_key('') # or await async_client.get_key('') -print(key_result.key) # X.509 private key in PEM format -print(key_result.certificate_chain) # Certificate chain -key_bytes = key_result.toBytes() # Get key as bytes +### Version Compatibility -# Generate TDX quote -quote_result = client.get_quote(report_data='some-data') # or await async_client.get_quote(report_data='some-data') -print(quote_result.quote) # TDX quote in hex format -print(quote_result.event_log) # Event log -rtmrs = quote_result.replay_rtmrs() # Replay RTMRs -``` +- **dstack OS 0.5.x**: Use `/var/run/dstack.sock` (current) +- **dstack OS 0.3.x**: Use `/var/run/tappd.sock` (deprecated but supported) -## Web3 Integration +The SDK automatically detects the correct socket path, but you must ensure the appropriate volume binding in your Docker Compose configuration. -The SDK provides integration with various blockchain ecosystems. You need to install the SDK with the appropriate optional dependencies: +## Advanced Features -```bash -# For Ethereum support -pip install "dstack-sdk[eth]" +### TLS Certificate Generation -# For Solana support -pip install "dstack-sdk[sol]" +Generate fresh TLS certificates with optional Remote Attestation support. **Important**: `get_tls_key()` generates random keys on each call - it's designed specifically for TLS/SSL scenarios where fresh keys are required. -# For both -pip install "dstack-sdk[all]" +```python +# Generate TLS certificate with different usage scenarios +tls_key = client.get_tls_key( + subject='my-secure-service', # Certificate common name + alt_names=['localhost', '127.0.0.1'], # Additional valid domains/IPs + usage_ra_tls=True, # Include remote attestation + usage_server_auth=True, # Enable server authentication (default) + usage_client_auth=False # Disable client authentication +) + +print('Private Key (PEM):', tls_key.key) +print('Certificate Chain:', tls_key.certificate_chain) + +# ⚠️ WARNING: Each call generates a different key +tls_key1 = client.get_tls_key() +tls_key2 = client.get_tls_key() +# tls_key1.key != tls_key2.key (always different!) ``` -### Ethereum +### Event Logging + +> [!NOTE] +> This feature isn't available in the simulator. We recommend sticking with `report_data` for most cases since it's simpler and safer to use. If you're not super familiar with SGX/TDX attestation quotes, it's best to avoid adding data directly into quotes as it could cause verification issues. + +Extend RTMR3 with custom events for audit trails: + +```python +# Emit custom events (requires dstack OS 0.5.0+) +client.emit_event('user-action', json.dumps({ + "action": "transfer", + "amount": 1000, + "timestamp": time.time() +})) + +# Events are automatically included in subsequent quotes +quote = client.get_quote(b'audit-data') +events = json.loads(quote.event_log) +``` + +## Blockchain Integration -You can use derived keys with the built-in Ethereum integration: +### Ethereum ```python -from dstack_sdk import DstackClient from dstack_sdk.ethereum import to_account -# Derive a key -client = DstackClient() -key_result = client.get_key('eth-account') +key_result = client.get_key('ethereum/main', 'wallet') # Convert to Ethereum account account = to_account(key_result) print(f"Ethereum address: {account.address}") -# Use with Web3 +# Use with Web3.py from web3 import Web3 -w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY')) +w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR-PROJECT-ID')) + # Sign transactions, etc. ``` ### Solana -For Solana integration, use the built-in helper: - ```python -from dstack_sdk import DstackClient from dstack_sdk.solana import to_keypair -# Derive a key -client = DstackClient() -key_result = client.get_key('solana-account') +key_result = client.get_key('solana/main', 'wallet') # Convert to Solana keypair keypair = to_keypair(key_result) -print(f"Solana public key: {keypair.pubkey()}") +print(f"Solana public key: {keypair.public_key}") + +# Use with solana-py +from solana.rpc.api import Client +from solana.transaction import Transaction +from solana.system_program import transfer, TransferParams -# Use with Solana transactions -# ... (sign transactions, etc.) +client_rpc = Client("https://api.mainnet-beta.solana.com") + +# Create and send transaction +# ... (implement transaction logic) ``` -## API Reference +## Environment Variables Encryption + +**Important**: This feature is specifically for **deployment-time security**, not runtime SDK operations. + +The SDK provides end-to-end encryption capabilities for securely transmitting sensitive environment variables during dstack application deployment. When deploying applications to TEE instances, sensitive configuration data (API keys, database credentials, private keys, etc.) needs to be securely transmitted from the deployment client to the TEE application. + +### Deployment Security Problem + +During application deployment, sensitive data must traverse: +1. **Client Environment** → Deployment infrastructure → **TEE Application** +2. **Risk**: Deployment infrastructure could potentially access plaintext secrets +3. **Solution**: Client-side encryption ensures only the target TEE application can decrypt secrets -### Running the Simulator +### How It Works -For local development without TDX devices, you can use the simulator under `sdk/simulator`. +1. **Pre-Deployment**: Client obtains encryption public key from KMS API +2. **Encryption**: Client encrypts environment variables using X25519 + AES-GCM +3. **Transmission**: Encrypted payload is sent through deployment infrastructure +4. **Decryption**: TEE application automatically decrypts and loads environment variables +5. **Runtime**: Application accesses secrets via standard `os.environ` -Run the simulator with: +This ensures **true end-to-end encryption** where deployment infrastructure never sees plaintext secrets. + +### App Configuration for Encrypted Variables + +Your `app-compose.json` should specify which environment variables are allowed: + +```json +{ + "manifest_version": 1, + "name": "secure-app", + "runner": "docker-compose", + "docker_compose_file": "services:\\n app:\\n build: .\\n volumes:\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n environment:\\n - API_KEY\\n - DATABASE_URL\\n - PRIVATE_KEY", + "allowed_envs": ["API_KEY", "DATABASE_URL", "PRIVATE_KEY"], + "kms_enabled": true +} +``` + +### Deployment Encryption Workflow + +```python +from dstack_sdk import encrypt_env_vars, verify_env_encrypt_public_key, EnvVar +import requests +import json + +# 1. Define sensitive environment variables +env_vars = [ + EnvVar(key='DATABASE_URL', value='postgresql://user:pass@host:5432/db'), + EnvVar(key='API_SECRET_KEY', value='your-secret-key'), + EnvVar(key='JWT_PRIVATE_KEY', value='-----BEGIN PRIVATE KEY-----\\n...'), + EnvVar(key='WALLET_MNEMONIC', value='abandon abandon abandon...'), +] + +# 2. Obtain encryption public key from KMS API (dstack-vmm or Phala Cloud) +response = requests.post('/prpc/GetAppEnvEncryptPubKey?json', + headers={'Content-Type': 'application/json'}, + json={'app_id': 'your-app-id-hex'}) +data = response.json() +public_key, signature = data['public_key'], data['signature'] + +# 3. Verify KMS API authenticity to prevent man-in-the-middle attacks +public_key_bytes = bytes.fromhex(public_key) +signature_bytes = bytes.fromhex(signature) + +trusted_pubkey = verify_env_encrypt_public_key(public_key_bytes, signature_bytes, 'your-app-id-hex') +if not trusted_pubkey: + raise RuntimeError('KMS API provided untrusted encryption key') + +print('Verified KMS public key:', trusted_pubkey.hex()) + +# 4. Encrypt environment variables for secure deployment +encrypted_data = encrypt_env_vars(env_vars, public_key) +print('Encrypted payload:', encrypted_data) + +# 5. Deploy with encrypted configuration +# deploy_dstack_app({ +# 'app_id': 'your-app-id-hex', +# 'encrypted_env': encrypted_data, +# # ... other deployment parameters +# }) +``` + +### Security Guarantees + +The environment encryption system provides several security guarantees: + +**End-to-End Encryption**: Environment variables are encrypted on the client side and can only be decrypted by the target dstack application inside the TEE. Even the deployment infrastructure cannot access the plaintext values. + +**KMS Authenticity Verification**: The `verify_env_encrypt_public_key` function validates that the encryption public key comes from a trusted KMS (Key Management Service), preventing man-in-the-middle attacks during key exchange. + +**Forward Secrecy**: Each encryption operation uses ephemeral X25519 keypairs, ensuring that compromising long-term keys cannot decrypt past communications. + +**Authenticated Encryption**: AES-256-GCM provides both confidentiality and integrity protection, detecting any tampering with encrypted data. + +## Cryptographic Security + +### Key Derivation Security + +The SDK implements secure key derivation using: + +- **Deterministic Generation**: Keys are derived using HMAC-based Key Derivation Function (HKDF) +- **Application Isolation**: Each path produces unique keys, preventing cross-application access +- **Signature Verification**: All derived keys include cryptographic proof of origin +- **TEE Protection**: Master keys never leave the secure enclave + +```python +# Each path generates a unique, deterministic key +wallet1 = client.get_key('app1/wallet', 'ethereum') +wallet2 = client.get_key('app2/wallet', 'ethereum') +# wallet1.key != wallet2.key (guaranteed different) + +same_wallet = client.get_key('app1/wallet', 'ethereum') +# wallet1.key == same_wallet.key (guaranteed identical) +``` + +### Remote Attestation + +TDX quotes provide cryptographic proof of: + +- **Code Integrity**: Measurement of loaded application code +- **Data Integrity**: Inclusion of application-specific data in quote +- **Environment Authenticity**: Verification of TEE platform and configuration + +```python +import json +import time + +application_state = json.dumps({ + "version": "1.0.0", + "config_hash": "sha256:...", + "timestamp": time.time() +}) + +quote = client.get_quote(application_state.encode()) + +# Quote can be verified by external parties to confirm: +# 1. Application is running in genuine TEE +# 2. Application code matches expected measurements +# 3. Application state is authentic and unmodified +``` + +### Environment Encryption Protocol + +The encryption scheme uses: + +- **X25519 ECDH**: Elliptic curve key exchange for forward secrecy +- **AES-256-GCM**: Authenticated encryption with 256-bit keys +- **Ephemeral Keys**: New keypair generated for each encryption operation +- **Authenticated Data**: Prevents tampering and ensures integrity + +## Development and Testing + +### Local Development + +For development without physical TDX hardware: ```bash +# Clone and build simulator git clone https://github.com/Dstack-TEE/dstack.git cd dstack/sdk/simulator ./build.sh ./dstack-simulator + +# Set environment variable +export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 +``` + +### Testing Connectivity + +```python +client = DstackClient() + +# Check if dstack service is available +is_available = client.is_reachable() +if not is_available: + print('dstack service is not reachable') + exit(1) ``` -### DstackClient and AsyncDstackClient +## API Reference + +### DstackClient / AsyncDstackClient #### Constructor + ```python DstackClient(endpoint: str | None = None) AsyncDstackClient(endpoint: str | None = None) ``` -- `endpoint`: Unix socket path or HTTP(S) URL. Defaults to '/var/run/dstack.sock'. -- Uses `DSTACK_SIMULATOR_ENDPOINT` environment variable if set -NOTE: Leave it empty in production. You only need to add `volumes` in your docker-compose file: +**Parameters:** +- `endpoint` (optional): Connection endpoint + - Unix socket path (production): `/var/run/dstack.sock` + - HTTP/HTTPS URL (development): `http://localhost:8090` + - Environment variable: `DSTACK_SIMULATOR_ENDPOINT` -```yaml - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock +**Production App Configuration:** + +The Docker Compose configuration is embedded in `app-compose.json`: + +```json +{ + "manifest_version": 1, + "name": "production-app", + "runner": "docker-compose", + "docker_compose_file": "services:\\n app:\\n image: your-app\\n volumes:\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n environment:\\n - NODE_ENV=production", + "public_tcbinfo": true +} ``` +**Important**: The `docker_compose_file` contains YAML content as a string, ensuring the volume binding for `/var/run/dstack.sock` is included. + #### Methods + +##### `info() -> InfoResponse` + +Retrieves comprehensive information about the TEE instance. + +**Returns:** `InfoResponse` +- `app_id`: Unique application identifier +- `instance_id`: Unique instance identifier +- `app_name`: Application name from configuration +- `device_id`: TEE device identifier +- `tcb_info`: Trusted Computing Base information + - `mrtd`: Measurement of TEE domain + - `rtmr0-3`: Runtime Measurement Registers + - `event_log`: Boot and runtime events + - `os_image_hash`: Operating system measurement + - `compose_hash`: Application configuration hash +- `app_cert`: Application certificate in PEM format +- `key_provider_info`: Key management configuration + ##### `get_key(path: str | None = None, purpose: str | None = None) -> GetKeyResponse` -Derives a key for the given path and purpose. +Derives a deterministic secp256k1/K256 private key for blockchain and Web3 applications. This is the primary method for obtaining cryptographic keys for wallets, signing, and other deterministic key scenarios. + +**Parameters:** +- `path`: Unique identifier for key derivation (e.g., `"wallet/ethereum"`, `"signing/solana"`) +- `purpose` (optional): Additional context for key usage (default: `""`) + +**Returns:** `GetKeyResponse` +- `key`: 32-byte secp256k1 private key as hex string (suitable for Ethereum, Bitcoin, Solana, etc.) +- `signature_chain`: Array of cryptographic signatures proving key authenticity + +**Key Characteristics:** +- **Deterministic**: Same path + purpose always generates identical key +- **Isolated**: Different paths produce cryptographically independent keys +- **Blockchain-Ready**: Compatible with secp256k1 curve (Ethereum, Bitcoin, Solana) +- **Verifiable**: Signature chain proves key was derived inside genuine TEE -- `path`: Path for key derivation (optional) -- `purpose`: Purpose for key derivation (optional) -- Returns: `GetKeyResponse` containing key and signature chain +**Use Cases:** +- Cryptocurrency wallets +- Transaction signing +- DeFi protocol interactions +- NFT operations +- Any scenario requiring consistent, reproducible keys + +```python +# Examples of deterministic key derivation +eth_wallet = client.get_key('wallet/ethereum', 'mainnet') +btc_wallet = client.get_key('wallet/bitcoin', 'mainnet') +sol_wallet = client.get_key('wallet/solana', 'mainnet') + +# Same path always returns same key +key1 = client.get_key('my-app/signing') +key2 = client.get_key('my-app/signing') +# key1.decode_key() == key2.decode_key() (guaranteed identical) + +# Different paths return different keys +user_a = client.get_key('user/alice/wallet') +user_b = client.get_key('user/bob/wallet') +# user_a.decode_key() != user_b.decode_key() (guaranteed different) +``` ##### `get_quote(report_data: str | bytes) -> GetQuoteResponse` -Generates a TDX quote with given report data. +Generates a TDX attestation quote containing the provided report data. -- `report_data`: Data to include in the quote (must be less than 64 bytes) -- Returns: `GetQuoteResponse` containing the quote and event log +**Parameters:** +- `report_data`: Data to include in quote (max 64 bytes) -##### `info() -> InfoResponse` +**Returns:** `GetQuoteResponse` +- `quote`: TDX quote as hex string +- `event_log`: JSON string of system events +- `replay_rtmrs()`: Method returning computed RTMR values + +**Use Cases:** +- Remote attestation of application state +- Cryptographic proof of execution environment +- Audit trail generation + +##### `get_tls_key(subject: str | None = None, alt_names: List[str] | None = None, usage_ra_tls: bool = False, usage_server_auth: bool = True, usage_client_auth: bool = False) -> GetTlsKeyResponse` + +Generates a fresh, random TLS key pair with X.509 certificate for TLS/SSL connections. **Important**: This method generates different keys on each call - use `get_key()` for deterministic keys. + +**Parameters:** +- `subject` (optional): Certificate subject (Common Name) - typically the domain name (default: `""`) +- `alt_names` (optional): Subject Alternative Names - additional domains/IPs for the certificate (default: `[]`) +- `usage_ra_tls` (optional): Include TDX attestation quote in certificate extension for remote verification (default: `False`) +- `usage_server_auth` (optional): Enable server authentication - allows certificate to authenticate servers (default: `True`) +- `usage_client_auth` (optional): Enable client authentication - allows certificate to authenticate clients (default: `False`) + +**Returns:** `GetTlsKeyResponse` +- `key`: Private key in PEM format (X.509/PKCS#8) +- `certificate_chain`: Certificate chain list + +**Key Characteristics:** +- **Random Generation**: Each call produces a completely different key +- **TLS-Optimized**: Keys and certificates designed for TLS/SSL scenarios +- **RA-TLS Support**: Optional remote attestation extension in certificates +- **TEE-Signed**: Certificates signed by TEE-resident Certificate Authority + +##### `emit_event(event: str, payload: str | bytes) -> None` + +Extends RTMR3 with a custom event for audit logging. + +**Parameters:** +- `event`: Event identifier string +- `payload`: Event data + +**Requirements:** +- dstack OS version 0.5.0 or later +- Events are permanently recorded in TEE measurements + +##### `is_reachable() -> bool` + +Tests connectivity to the dstack service. + +**Returns:** `bool` indicating service availability + +## Utility Functions + +### Compose Hash Calculation -Retrieves information about the CVM instance. +```python +from dstack_sdk import get_compose_hash + +app_compose = { + "manifest_version": 1, + "name": "my-app", + "runner": "docker-compose", + "docker_compose_file": "docker-compose.yml" +} + +hash_value = get_compose_hash(app_compose) +print('Configuration hash:', hash_value) +``` + +### KMS Public Key Verification + +Verify the authenticity of encryption public keys provided by KMS APIs: + +```python +from dstack_sdk import verify_env_encrypt_public_key + +# Example: Verify KMS-provided encryption key +public_key = bytes.fromhex('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a') +signature = bytes.fromhex('8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00') +app_id = '0000000000000000000000000000000000000000' + +kms_identity = verify_env_encrypt_public_key(public_key, signature, app_id) + +if kms_identity: + print('Trusted KMS identity:', kms_identity.hex()) + # Safe to use the public key for encryption +else: + print('KMS signature verification failed') + # Potential man-in-the-middle attack +``` + +## Security Best Practices + +1. **Key Management** + - Use descriptive, unique paths for key derivation + - Never expose derived keys outside the TEE + - Implement proper access controls in your application + +2. **Remote Attestation** + - Always verify quotes before trusting remote TEE instances + - Include application-specific data in quote generation + - Validate RTMR measurements against expected values + +3. **TLS Configuration** + - Enable RA-TLS for attestation-based authentication + - Use appropriate certificate validity periods + - Implement proper certificate validation + +4. **Error Handling** + - Handle cryptographic operation failures gracefully + - Log security events for monitoring + - Implement fallback mechanisms where appropriate + +## Migration Guide + +### Critical API Changes: Understanding the Separation + +The legacy client mixed two different use cases that have now been properly separated: -- Returns: `InfoResponse` containing app_id, instance_id, and tcb_info +1. **`get_key()`**: Deterministic key derivation for Web3/blockchain (secp256k1) +2. **`get_tls_key()`**: Random TLS certificate generation for HTTPS/SSL -##### `get_tls_key(subject: str | None = None, alt_names: List[str] | None = None, usage_ra_tls: bool = False, usage_server_auth: bool = False, usage_client_auth: bool = False) -> GetTlsKeyResponse` +### From TappdClient to DstackClient -Gets a TLS key from the dstack service with optional parameters. +**⚠️ BREAKING CHANGE**: `TappdClient` and `AsyncTappdClient` are deprecated and will be removed. All users must migrate to `DstackClient` and `AsyncDstackClient`. -- `subject`: The subject for the TLS key (optional) -- `alt_names`: Alternative names for the TLS key (optional) -- `usage_ra_tls`: Whether to enable RA TLS usage (default: False) -- `usage_server_auth`: Whether to enable server auth usage (default: False) -- `usage_client_auth`: Whether to enable client auth usage (default: False) -- Returns: `GetTlsKeyResponse` containing the key and certificate chain +### Complete Migration Reference +| Component | TappdClient (Old) | DstackClient (New) | Status | +|-----------|-------------------|-------------------|---------| +| **Socket Path** | `/var/run/tappd.sock` | `/var/run/dstack.sock` | ✅ Updated | +| **HTTP URL Format** | `http://localhost/prpc/Tappd.` | `http://localhost/` | ✅ Simplified | +| **K256 Key Method** | `get_key(...)` | `get_key(...)` | ✅ Same | +| **TLS Certificate Method** | `derive_key(...)` | `get_tls_key(...)` | ✅ Separated | +| **TDX Quote (Raw)** | `tdx_quote(...)` | `get_quote(report_data)` | ✅ Renamed | + +#### Migration Steps + +**Step 1: Update Imports and Client** + +```python +# Before +from dstack_sdk import TappdClient, AsyncTappdClient +client = TappdClient() +async_client = AsyncTappdClient() + +# After +from dstack_sdk import DstackClient, AsyncDstackClient +client = DstackClient() +async_client = AsyncDstackClient() +``` + +**Step 2: Update Method Calls** + +```python +# For deterministic keys (most common) +# Before: TappdClient methods +key_result = client.get_key('wallet', 'ethereum') + +# After: DstackClient methods (same!) +key_result = client.get_key('wallet', 'ethereum') + +# For TLS certificates +# Before: derive_key with TLS options +tls_cert = client.derive_key('api', 'example.com', ['localhost']) + +# After: get_tls_key with proper options +tls_cert = client.get_tls_key( + subject='example.com', + alt_names=['localhost'] +) +``` + +### Migration Checklist + +- [ ] **Infrastructure Updates:** + - [ ] Update Docker volume binding to `/var/run/dstack.sock` + - [ ] Change environment variables from `TAPPD_*` to `DSTACK_*` + +- [ ] **Client Code Updates:** + - [ ] Replace `TappdClient` with `DstackClient` + - [ ] Replace `AsyncTappdClient` with `AsyncDstackClient` + - [ ] Replace `derive_key()` calls with `get_tls_key()` for TLS certificates + - [ ] Replace `tdx_quote()` calls with `get_quote()` + +- [ ] **Testing:** + - [ ] Test that deterministic keys still work as expected + - [ ] Verify TLS certificate generation works + - [ ] Test quote generation with new interface ## Development We use [PDM](https://pdm-project.org/en/latest/) for local development and creating an isolated environment. -Just run the following command to initiate development: +To initiate development: ```bash pdm install -d ``` +To run tests: + ```bash -DSTACK_SIMULATOR_ENDPOINT=$(realpath ../simulator/dstack.sock) pdm run pytest +DSTACK_SIMULATOR_ENDPOINT=/path/to/dstack/sdk/simulator/dstack.sock pdm run pytest -s ``` ## License -Apache License - +Apache License 2.0 \ No newline at end of file From 09a7450e246a3dc244d11a1f2ea445def1ebf583 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Thu, 14 Aug 2025 21:29:43 +0800 Subject: [PATCH 05/20] docs(sdk/python): added explaination for sync & async. --- sdk/python/README.md | 143 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 9 deletions(-) diff --git a/sdk/python/README.md b/sdk/python/README.md index 59241a09e..8b81ef76f 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -12,6 +12,24 @@ pip install dstack-sdk The dstack SDK enables secure communication with dstack Trusted Execution Environment (TEE) instances. dstack applications are defined using `app-compose.json` (based on the `AppCompose` structure) and deployed as containerized applications using Docker Compose. +### Client Types + +The Python SDK provides both synchronous and asynchronous clients to accommodate different programming patterns: + +#### Synchronous Clients +- **`DstackClient`**: Main synchronous client for dstack services +- **`TappdClient`**: Deprecated synchronous client (use `DstackClient` instead) + +#### Asynchronous Clients +- **`AsyncDstackClient`**: Async/await compatible client for dstack services +- **`AsyncTappdClient`**: Deprecated async client (use `AsyncDstackClient` instead) + +**Key Differences:** +- **Synchronous clients** use regular method calls and block until completion +- **Asynchronous clients** use `async`/`await` syntax and allow concurrent operations +- **API compatibility**: Both client types provide identical methods and functionality +- **Performance**: Async clients excel in I/O-bound applications with multiple concurrent requests + ### Application Architecture dstack applications consist of: @@ -63,20 +81,19 @@ First, ensure your dstack application is properly configured: **Note**: The `docker_compose_file` field contains the actual Docker Compose YAML content as a string, not a file path. -### SDK Integration +### Synchronous Client Usage ```python -from dstack_sdk import DstackClient, AsyncDstackClient +import json +import time +from dstack_sdk import DstackClient -# Create client - automatically connects to /var/run/dstack.sock +# Create synchronous client - automatically connects to /var/run/dstack.sock client = DstackClient() # For local development with simulator dev_client = DstackClient('http://localhost:8090') -# Async client -async_client = AsyncDstackClient() - # Get TEE instance information info = client.info() print('App ID:', info.app_id) @@ -105,6 +122,75 @@ rtmrs = quote.replay_rtmrs() print('RTMR0-3:', rtmrs) ``` +### Asynchronous Client Usage + +```python +import json +import time +import asyncio +from dstack_sdk import AsyncDstackClient + +async def main(): + # Create asynchronous client + async_client = AsyncDstackClient() + + # All methods must be awaited + info = await async_client.info() + print('App ID:', info.app_id) + + # Derive keys asynchronously + wallet_key = await async_client.get_key('wallet/ethereum', 'mainnet') + print('Derived key:', wallet_key.decode_key().hex()) + + # Generate quote asynchronously + quote = await async_client.get_quote(b'async-test-data') + print('Quote length:', len(quote.quote)) + + # Multiple concurrent operations + tasks = [ + async_client.get_key('wallet/btc', 'mainnet'), + async_client.get_key('wallet/eth', 'mainnet'), + async_client.get_key('signing/key', 'production') + ] + + # Execute concurrently + keys = await asyncio.gather(*tasks) + for i, key in enumerate(keys): + print(f'Key {i}: {key.decode_key().hex()[:16]}...') + +# Run async code +if __name__ == "__main__": + asyncio.run(main()) +``` + +### Choosing Between Sync and Async + +**Use Synchronous Client (`DstackClient`) when:** +- Building simple scripts or CLI tools +- Making sequential API calls +- Working in synchronous codebases +- Learning or prototyping + +**Use Asynchronous Client (`AsyncDstackClient`) when:** +- Building web applications (FastAPI, Starlette, etc.) +- Needing concurrent TEE operations +- Integrating with async frameworks +- Optimizing I/O-bound applications + +```python +# Example: Concurrent key derivation (async advantage) +async def derive_multiple_keys(): + client = AsyncDstackClient() + + # This is much faster than sequential sync calls + keys = await asyncio.gather( + client.get_key('user/alice', 'eth'), + client.get_key('user/bob', 'eth'), + client.get_key('user/charlie', 'eth') + ) + return keys +``` + ### Version Compatibility - **dstack OS 0.5.x**: Use `/var/run/dstack.sock` (current) @@ -380,13 +466,23 @@ if not is_available: ## API Reference -### DstackClient / AsyncDstackClient +### Client Classes Overview + +The Python SDK provides four client classes with identical functionality but different execution models: + +| Client Class | Type | Status | Use Case | +|-------------|------|--------|----------| +| `DstackClient` | Synchronous | ✅ Active | General use, scripts, synchronous code | +| `AsyncDstackClient` | Asynchronous | ✅ Active | Web apps, concurrent operations | +| `TappdClient` | Synchronous | ⚠️ Deprecated | Legacy compatibility only | +| `AsyncTappdClient` | Asynchronous | ⚠️ Deprecated | Legacy compatibility only | + +### DstackClient (Synchronous) #### Constructor ```python DstackClient(endpoint: str | None = None) -AsyncDstackClient(endpoint: str | None = None) ``` **Parameters:** @@ -395,6 +491,22 @@ AsyncDstackClient(endpoint: str | None = None) - HTTP/HTTPS URL (development): `http://localhost:8090` - Environment variable: `DSTACK_SIMULATOR_ENDPOINT` +### AsyncDstackClient (Asynchronous) + +#### Constructor + +```python +AsyncDstackClient(endpoint: str | None = None) +``` + +**Parameters:** Same as `DstackClient` + +**Key Differences from Sync Client:** +- All methods are `async` and must be awaited +- Supports concurrent operations with `asyncio.gather()` +- Better performance for multiple I/O operations +- Integrates with async frameworks (FastAPI, aiohttp, etc.) + **Production App Configuration:** The Docker Compose configuration is embedded in `app-compose.json`: @@ -411,9 +523,22 @@ The Docker Compose configuration is embedded in `app-compose.json`: **Important**: The `docker_compose_file` contains YAML content as a string, ensuring the volume binding for `/var/run/dstack.sock` is included. +### Shared Methods + +All methods below are available in both synchronous and asynchronous clients with identical signatures and functionality: + +| Sync Method | Async Method | Description | +|------------|--------------|-------------| +| `client.info()` | `await async_client.info()` | Get TEE instance information | +| `client.get_key(...)` | `await async_client.get_key(...)` | Derive deterministic keys | +| `client.get_quote(...)` | `await async_client.get_quote(...)` | Generate attestation quote | +| `client.get_tls_key(...)` | `await async_client.get_tls_key(...)` | Generate TLS certificate | +| `client.emit_event(...)` | `await async_client.emit_event(...)` | Log custom events | +| `client.is_reachable()` | `await async_client.is_reachable()` | Test connectivity | + #### Methods -##### `info() -> InfoResponse` +##### `info() -> InfoResponse` / `async info() -> InfoResponse` Retrieves comprehensive information about the TEE instance. From b3038e076a1bee185ac366215738b7daeb0b3726 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Fri, 15 Aug 2025 02:33:04 +0000 Subject: [PATCH 06/20] refactor(sdk/python): update RPC method calls to use method names instead of paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PATH_PREFIX constant to both DstackClient and AsyncDstackClient - Change _send_rpc_request parameter from 'path' to 'method' - Construct full path internally using PATH_PREFIX + method - Update all RPC calls to pass method names without leading slash 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- sdk/python/src/dstack_sdk/dstack_client.py | 40 +++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 18642c74b..4446854a8 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -198,6 +198,8 @@ class BaseClient: class DstackClient(BaseClient): """Synchronous client for dstack services.""" + PATH_PREFIX = "/" + def __init__(self, endpoint: str | None = None): """Initialize client with HTTP or Unix-socket transport. @@ -215,8 +217,9 @@ def __init__(self, endpoint: str | None = None): self.transport = httpx.HTTPTransport(uds=endpoint) self.base_url = "http://localhost" - def _send_rpc_request(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]: + def _send_rpc_request(self, method: str, payload: Dict[str, Any]) -> Dict[str, Any]: """Send an RPC request and return the parsed JSON response.""" + path = self.PATH_PREFIX + method with httpx.Client(transport=self.transport, base_url=self.base_url) as client: response = client.post( path, @@ -238,7 +241,7 @@ def get_key( ) -> GetKeyResponse: """Derive a key from the given path and purpose.""" data: Dict[str, Any] = {"path": path or "", "purpose": purpose or ""} - result = self._send_rpc_request("/GetKey", data) + result = self._send_rpc_request("GetKey", data) return GetKeyResponse(**result) def get_quote( @@ -254,12 +257,12 @@ def get_quote( if len(report_bytes) > 64: raise ValueError("report_data must be less than 64 bytes") hex = binascii.hexlify(report_bytes).decode() - result = self._send_rpc_request("/GetQuote", {"report_data": hex}) + result = self._send_rpc_request("GetQuote", {"report_data": hex}) return GetQuoteResponse(**result) def info(self) -> InfoResponse: """Fetch service information including parsed TCB info.""" - result = self._send_rpc_request("/Info", {}) + result = self._send_rpc_request("Info", {}) return InfoResponse.parse_response(result) def emit_event( @@ -273,7 +276,7 @@ def emit_event( payload_bytes: bytes = payload.encode() if isinstance(payload, str) else payload hex_payload = binascii.hexlify(payload_bytes).decode() - self._send_rpc_request("/EmitEvent", {"event": event, "payload": hex_payload}) + self._send_rpc_request("EmitEvent", {"event": event, "payload": hex_payload}) return None def get_tls_key( @@ -294,7 +297,7 @@ def get_tls_key( if alt_names: data["alt_names"] = list(alt_names) - result = self._send_rpc_request("/GetTlsKey", data) + result = self._send_rpc_request("GetTlsKey", data) return GetTlsKeyResponse(**result) def is_reachable(self) -> bool: @@ -320,6 +323,8 @@ def is_reachable(self) -> bool: class AsyncDstackClient(BaseClient): """Asynchronous client for dstack services.""" + PATH_PREFIX = "/" + def __init__(self, endpoint: str | None = None): """Initialize async client with HTTP or Unix-socket transport.""" endpoint = get_endpoint(endpoint) @@ -334,9 +339,10 @@ def __init__(self, endpoint: str | None = None): self.base_url = "http://localhost" async def _send_rpc_request( - self, path: str, payload: Dict[str, Any] + self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: """Send an RPC request asynchronously and return parsed JSON.""" + path = self.PATH_PREFIX + method async with httpx.AsyncClient( transport=self.transport, base_url=self.base_url ) as client: @@ -360,7 +366,7 @@ async def get_key( ) -> GetKeyResponse: """Derive a key from the given path and purpose.""" data: Dict[str, Any] = {"path": path or "", "purpose": purpose or ""} - result = await self._send_rpc_request("/GetKey", data) + result = await self._send_rpc_request("GetKey", data) return GetKeyResponse(**result) async def get_quote( @@ -376,12 +382,12 @@ async def get_quote( if len(report_bytes) > 64: raise ValueError("report_data must be less than 64 bytes") hex = binascii.hexlify(report_bytes).decode() - result = await self._send_rpc_request("/GetQuote", {"report_data": hex}) + result = await self._send_rpc_request("GetQuote", {"report_data": hex}) return GetQuoteResponse(**result) async def info(self) -> InfoResponse: """Fetch service information including parsed TCB info.""" - result = await self._send_rpc_request("/Info", {}) + result = await self._send_rpc_request("Info", {}) return InfoResponse.parse_response(result) async def emit_event( @@ -396,7 +402,7 @@ async def emit_event( payload_bytes: bytes = payload.encode() if isinstance(payload, str) else payload hex_payload = binascii.hexlify(payload_bytes).decode() await self._send_rpc_request( - "/EmitEvent", {"event": event, "payload": hex_payload} + "EmitEvent", {"event": event, "payload": hex_payload} ) return None @@ -418,7 +424,7 @@ async def get_tls_key( if alt_names: data["alt_names"] = list(alt_names) - result = await self._send_rpc_request("/GetTlsKey", data) + result = await self._send_rpc_request("GetTlsKey", data) return GetTlsKeyResponse(**result) async def is_reachable(self) -> bool: @@ -446,6 +452,7 @@ class TappdClient(DstackClient): DEPRECATED: Use ``DstackClient`` instead. """ + PATH_PREFIX = "/prpc/Tappd." def __init__(self, endpoint: str | None = None): """Initialize deprecated tappd client wrapper.""" @@ -482,7 +489,7 @@ def derive_key( if alt_names: data["alt_names"] = alt_names - result = self._send_rpc_request("/prpc/Tappd.DeriveKey", data) + result = self._send_rpc_request("DeriveKey", data) return GetTlsKeyResponse(**result) def tdx_quote( @@ -517,7 +524,7 @@ def tdx_quote( payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} - result = self._send_rpc_request("/prpc/Tappd.TdxQuote", payload) + result = self._send_rpc_request("TdxQuote", payload) if "error" in result: raise RuntimeError(result["error"]) @@ -530,6 +537,7 @@ class AsyncTappdClient(AsyncDstackClient): DEPRECATED: Use ``AsyncDstackClient`` instead. """ + PATH_PREFIX = "/prpc/Tappd." def __init__(self, endpoint: str | None = None): """Initialize deprecated async tappd client wrapper.""" @@ -566,7 +574,7 @@ async def derive_key( if alt_names: data["alt_names"] = alt_names - result = await self._send_rpc_request("/prpc/Tappd.DeriveKey", data) + result = await self._send_rpc_request("DeriveKey", data) return GetTlsKeyResponse(**result) async def tdx_quote( @@ -601,7 +609,7 @@ async def tdx_quote( payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} - result = await self._send_rpc_request("/prpc/Tappd.TdxQuote", payload) + result = await self._send_rpc_request("TdxQuote", payload) if "error" in result: raise RuntimeError(result["error"]) From 8704adcab20429bc220524d9b4c98efd1048c516 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Fri, 15 Aug 2025 02:42:09 +0000 Subject: [PATCH 07/20] refactor(sdk/python): simplify is_reachable methods to use _send_rpc_request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace direct HTTP client calls with _send_rpc_request("Version", {}) - Consistent with other methods in the class - Reduces code duplication and maintains abstraction 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- sdk/python/src/dstack_sdk/dstack_client.py | 30 +++------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 4446854a8..6db844d40 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -303,19 +303,8 @@ def get_tls_key( def is_reachable(self) -> bool: """Return True if the service responds to a quick health call.""" try: - with httpx.Client( - transport=self.transport, base_url=self.base_url, timeout=0.5 - ) as client: - response = client.post( - "/prpc/Tappd.Info", - json={}, - headers={ - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - }, - ) - response.raise_for_status() - return True + self._send_rpc_request("Version", {}) + return True except Exception: return False @@ -430,19 +419,8 @@ async def get_tls_key( async def is_reachable(self) -> bool: """Return True if the service responds to a quick health call.""" try: - async with httpx.AsyncClient( - transport=self.transport, base_url=self.base_url, timeout=0.5 - ) as client: - response = await client.post( - "/prpc/Tappd.Info", - json={}, - headers={ - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - }, - ) - response.raise_for_status() - return True + await self._send_rpc_request("Version", {}) + return True except Exception: return False From faeffc2c32c1a836b93625f3363d6b06fc102035 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Fri, 15 Aug 2025 04:52:29 +0000 Subject: [PATCH 08/20] sdk/python: Reuse code logic between async and sync clients --- sdk/python/src/dstack_sdk/dstack_client.py | 369 +++++++++------------ sdk/python/tests/test_client.py | 59 +++- 2 files changed, 212 insertions(+), 216 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 6db844d40..271826b14 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -65,7 +65,8 @@ def get_tappd_endpoint(endpoint: str | None = None) -> str: if endpoint: return endpoint if "TAPPD_SIMULATOR_ENDPOINT" in os.environ: - logger.info(f"Using tappd endpoint: {os.environ['TAPPD_SIMULATOR_ENDPOINT']}") + logger.info( + f"Using tappd endpoint: {os.environ['TAPPD_SIMULATOR_ENDPOINT']}") return os.environ["TAPPD_SIMULATOR_ENDPOINT"] return "/var/run/tappd.sock" @@ -195,158 +196,74 @@ class BaseClient: """Marker base class for dstack clients.""" -class DstackClient(BaseClient): - """Synchronous client for dstack services.""" +class AsyncDstackClient(BaseClient): + """Asynchronous client for dstack services. + + This client can work in both async and sync contexts by using + sync HTTP internally while maintaining async method signatures. + """ PATH_PREFIX = "/" - def __init__(self, endpoint: str | None = None): - """Initialize client with HTTP or Unix-socket transport. + def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): + """Initialize async client with HTTP or Unix-socket transport. - If a non-HTTP(S) endpoint is provided, it is treated as a Unix socket - path and validated for existence. + Args: + endpoint: HTTP/HTTPS URL or Unix socket path + use_sync_http: If True, use sync HTTP client internally """ endpoint = get_endpoint(endpoint) - if endpoint.startswith("http://") or endpoint.startswith("https://"): - self.transport = httpx.HTTPTransport() - self.base_url = endpoint - else: - # Check if Unix socket file exists - if endpoint.startswith("/") and not os.path.exists(endpoint): - raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") - self.transport = httpx.HTTPTransport(uds=endpoint) - self.base_url = "http://localhost" - - def _send_rpc_request(self, method: str, payload: Dict[str, Any]) -> Dict[str, Any]: - """Send an RPC request and return the parsed JSON response.""" - path = self.PATH_PREFIX + method - with httpx.Client(transport=self.transport, base_url=self.base_url) as client: - response = client.post( - path, - json=payload, - headers={ - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - }, - ) - response.raise_for_status() - from typing import cast - - return cast(Dict[str, Any], response.json()) - - def get_key( - self, - path: str | None = None, - purpose: str | None = None, - ) -> GetKeyResponse: - """Derive a key from the given path and purpose.""" - data: Dict[str, Any] = {"path": path or "", "purpose": purpose or ""} - result = self._send_rpc_request("GetKey", data) - return GetKeyResponse(**result) - - def get_quote( - self, - report_data: str | bytes, - ) -> GetQuoteResponse: - """Request an attestation quote for the provided report data.""" - if not report_data or not isinstance(report_data, (bytes, str)): - raise ValueError("report_data can not be empty") - report_bytes: bytes = ( - report_data.encode() if isinstance(report_data, str) else report_data - ) - if len(report_bytes) > 64: - raise ValueError("report_data must be less than 64 bytes") - hex = binascii.hexlify(report_bytes).decode() - result = self._send_rpc_request("GetQuote", {"report_data": hex}) - return GetQuoteResponse(**result) - - def info(self) -> InfoResponse: - """Fetch service information including parsed TCB info.""" - result = self._send_rpc_request("Info", {}) - return InfoResponse.parse_response(result) - - def emit_event( - self, - event: str, - payload: str | bytes, - ) -> None: - """Emit an event that extends RTMR3 on TDX platforms.""" - if not event: - raise ValueError("event name cannot be empty") - - payload_bytes: bytes = payload.encode() if isinstance(payload, str) else payload - hex_payload = binascii.hexlify(payload_bytes).decode() - self._send_rpc_request("EmitEvent", {"event": event, "payload": hex_payload}) - return None - - def get_tls_key( - self, - subject: str | None = None, - alt_names: List[str] | None = None, - usage_ra_tls: bool = False, - usage_server_auth: bool = True, - usage_client_auth: bool = False, - ) -> GetTlsKeyResponse: - """Request a TLS key from the service with optional parameters.""" - data: Dict[str, Any] = { - "subject": subject or "", - "usage_ra_tls": usage_ra_tls, - "usage_server_auth": usage_server_auth, - "usage_client_auth": usage_client_auth, - } - if alt_names: - data["alt_names"] = list(alt_names) - - result = self._send_rpc_request("GetTlsKey", data) - return GetTlsKeyResponse(**result) - - def is_reachable(self) -> bool: - """Return True if the service responds to a quick health call.""" - try: - self._send_rpc_request("Version", {}) - return True - except Exception: - return False - + self.use_sync_http = use_sync_http -class AsyncDstackClient(BaseClient): - """Asynchronous client for dstack services.""" - - PATH_PREFIX = "/" - - def __init__(self, endpoint: str | None = None): - """Initialize async client with HTTP or Unix-socket transport.""" - endpoint = get_endpoint(endpoint) if endpoint.startswith("http://") or endpoint.startswith("https://"): - self.transport = httpx.AsyncHTTPTransport() + if use_sync_http: + self.transport = httpx.HTTPTransport() + else: + self.transport = httpx.AsyncHTTPTransport() self.base_url = endpoint else: # Check if Unix socket file exists if endpoint.startswith("/") and not os.path.exists(endpoint): - raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") - self.transport = httpx.AsyncHTTPTransport(uds=endpoint) + raise FileNotFoundError( + f"Unix socket file {endpoint} does not exist") + if use_sync_http: + self.transport = httpx.HTTPTransport(uds=endpoint) + else: + self.transport = httpx.AsyncHTTPTransport(uds=endpoint) self.base_url = "http://localhost" async def _send_rpc_request( self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: - """Send an RPC request asynchronously and return parsed JSON.""" + """Send an RPC request and return parsed JSON. + + Uses sync or async HTTP client based on use_sync_http flag. + Maintains async signature for compatibility. + """ path = self.PATH_PREFIX + method - async with httpx.AsyncClient( - transport=self.transport, base_url=self.base_url - ) as client: - response = await client.post( - path, - json=payload, - headers={ - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - }, - ) - response.raise_for_status() - from typing import cast - - return cast(Dict[str, Any], response.json()) + headers = { + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + } + + if self.use_sync_http: + # Use sync HTTP client - works from any context + with httpx.Client( + transport=self.transport, base_url=self.base_url + ) as client: + response = client.post(path, json=payload, headers=headers) + response.raise_for_status() + from typing import cast + return cast(Dict[str, Any], response.json()) + else: + # Use async HTTP client - traditional async behavior + async with httpx.AsyncClient( + transport=self.transport, base_url=self.base_url + ) as client: + response = await client.post(path, json=payload, headers=headers) + response.raise_for_status() + from typing import cast + return cast(Dict[str, Any], response.json()) async def get_key( self, @@ -419,33 +336,33 @@ async def get_tls_key( async def is_reachable(self) -> bool: """Return True if the service responds to a quick health call.""" try: - await self._send_rpc_request("Version", {}) + await self._send_rpc_request("Info", {}) return True except Exception: return False -class TappdClient(DstackClient): - """Deprecated client kept for backward compatibility. +class AsyncTappdClient(AsyncDstackClient): + """Deprecated async client kept for backward compatibility. - DEPRECATED: Use ``DstackClient`` instead. + DEPRECATED: Use ``AsyncDstackClient`` instead. """ PATH_PREFIX = "/prpc/Tappd." - def __init__(self, endpoint: str | None = None): - """Initialize deprecated tappd client wrapper.""" + def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): + """Initialize deprecated async tappd client wrapper.""" import warnings warnings.warn( - "TappdClient is deprecated, please use DstackClient instead", + "AsyncTappdClient is deprecated, please use AsyncDstackClient instead", DeprecationWarning, stacklevel=2, ) endpoint = get_tappd_endpoint(endpoint) - super().__init__(endpoint) + super().__init__(endpoint, use_sync_http=use_sync_http) - def derive_key( + async def derive_key( self, path: str | None = None, subject: str | None = None, @@ -467,10 +384,10 @@ def derive_key( if alt_names: data["alt_names"] = alt_names - result = self._send_rpc_request("DeriveKey", data) + result = await self._send_rpc_request("DeriveKey", data) return GetTlsKeyResponse(**result) - def tdx_quote( + async def tdx_quote( self, report_data: str | bytes, hash_algorithm: str | None = None, @@ -500,9 +417,10 @@ def tdx_quote( if len(hex_data) < 128: hex_data = hex_data.zfill(128) - payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} + payload = {"report_data": hex_data, + "hash_algorithm": hash_algorithm or "raw"} - result = self._send_rpc_request("TdxQuote", payload) + result = await self._send_rpc_request("TdxQuote", payload) if "error" in result: raise RuntimeError(result["error"]) @@ -510,86 +428,117 @@ def tdx_quote( return GetQuoteResponse(**result) -class AsyncTappdClient(AsyncDstackClient): - """Deprecated async client kept for backward compatibility. +def call_async(func): + """Decorator to call async methods synchronously. - DEPRECATED: Use ``AsyncDstackClient`` instead. + This decorator wraps a method to call its async counterpart from + self.async_client and run it synchronously using asyncio. + + Supports being called from within async contexts by using + a sync HTTP client internally and a custom coroutine runner. """ - PATH_PREFIX = "/prpc/Tappd." + import functools - def __init__(self, endpoint: str | None = None): - """Initialize deprecated async tappd client wrapper.""" - import warnings + def _step_coro(coro): + """Step through a coroutine that only does sync operations.""" + try: + result = coro.send(None) + raise RuntimeError(f"Coroutine yielded unexpected value: {result}") + except StopIteration as e: + return e.value - warnings.warn( - "AsyncTappdClient is deprecated, please use AsyncDstackClient instead", - DeprecationWarning, - stacklevel=2, - ) + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + async_method = getattr(self.async_client, func.__name__) + return _step_coro(async_method(*args, **kwargs)) + return wrapper - endpoint = get_tappd_endpoint(endpoint) - super().__init__(endpoint) - async def derive_key( - self, - path: str | None = None, - subject: str | None = None, - alt_names: List[str] | None = None, - ) -> GetTlsKeyResponse: - """Use ``get_key`` instead (deprecated).""" - import warnings +class DstackClient(BaseClient): + """Synchronous client for dstack services.""" - warnings.warn( - "derive_key is deprecated, please use get_key instead", - DeprecationWarning, - stacklevel=2, - ) + def __init__(self, endpoint: str | None = None): + """Initialize client with HTTP or Unix-socket transport. - data: Dict[str, Any] = { - "path": path or "", - "subject": subject or path or "", - } - if alt_names: - data["alt_names"] = alt_names + If a non-HTTP(S) endpoint is provided, it is treated as a Unix socket + path and validated for existence. + """ + self.async_client = AsyncDstackClient(endpoint, use_sync_http=True) - result = await self._send_rpc_request("DeriveKey", data) - return GetTlsKeyResponse(**result) + @call_async + def get_key( + self, + path: str | None = None, + purpose: str | None = None, + ) -> GetKeyResponse: + """Derive a key from the given path and purpose.""" + raise NotImplementedError - async def tdx_quote( + @call_async + def get_quote( self, report_data: str | bytes, - hash_algorithm: str | None = None, ) -> GetQuoteResponse: - """Use ``get_quote`` instead (deprecated).""" - import warnings + """Request an attestation quote for the provided report data.""" + raise NotImplementedError - warnings.warn( - "tdx_quote is deprecated, please use get_quote instead", - DeprecationWarning, - stacklevel=2, - ) + @call_async + def info(self) -> InfoResponse: + """Fetch service information including parsed TCB info.""" + raise NotImplementedError - if not report_data or not isinstance(report_data, (bytes, str)): - raise ValueError("report_data can not be empty") + @call_async + def emit_event( + self, + event: str, + payload: str | bytes, + ) -> None: + """Emit an event that extends RTMR3 on TDX platforms.""" + raise NotImplementedError - report_bytes: bytes = ( - report_data.encode() if isinstance(report_data, str) else report_data - ) - hex_data = binascii.hexlify(report_bytes).decode() + @call_async + def get_tls_key( + self, + subject: str | None = None, + alt_names: List[str] | None = None, + usage_ra_tls: bool = False, + usage_server_auth: bool = True, + usage_client_auth: bool = False, + ) -> GetTlsKeyResponse: + """Request a TLS key from the service with optional parameters.""" + raise NotImplementedError - if hash_algorithm == "raw": - if len(hex_data) > 128: - raise ValueError( - "Report data is too large, it should less then 64 bytes when hash_algorithm is raw." - ) - if len(hex_data) < 128: - hex_data = hex_data.zfill(128) + @call_async + def is_reachable(self) -> bool: + """Return True if the service responds to a quick health call.""" + raise NotImplementedError - payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} - result = await self._send_rpc_request("TdxQuote", payload) +class TappdClient(DstackClient): + """Deprecated client kept for backward compatibility. - if "error" in result: - raise RuntimeError(result["error"]) + DEPRECATED: Use ``DstackClient`` instead. + """ - return GetQuoteResponse(**result) + def __init__(self, endpoint: str | None = None): + """Initialize deprecated tappd client wrapper.""" + self.async_client = AsyncTappdClient(endpoint, use_sync_http=True) + + @call_async + def derive_key( + self, + path: str | None = None, + subject: str | None = None, + alt_names: List[str] | None = None, + ) -> GetTlsKeyResponse: + """Use ``get_key`` instead (deprecated).""" + raise NotImplementedError + + @call_async + def tdx_quote( + self, + report_data: str | bytes, + hash_algorithm: str | None = None, + ) -> GetQuoteResponse: + """Use ``get_quote`` instead (deprecated).""" + raise NotImplementedError diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 2b667d8d8..9e9f47cf5 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -142,6 +142,7 @@ def test_sync_client_is_reachable(): client = DstackClient() is_reachable = client.is_reachable() assert isinstance(is_reachable, bool) + assert is_reachable @pytest.mark.asyncio @@ -150,6 +151,7 @@ async def test_async_client_is_reachable(): client = AsyncDstackClient() is_reachable = await client.is_reachable() assert isinstance(is_reachable, bool) + assert is_reachable def test_tls_key_as_uint8array(): @@ -274,8 +276,10 @@ def test_tappd_client_derive_key_deprecated(): client.derive_key("/", "test") # Should have warnings for both constructor and derive_key warning_messages = [str(warning.message) for warning in w] - assert any("TappdClient is deprecated" in msg for msg in warning_messages) - assert any("derive_key is deprecated" in msg for msg in warning_messages) + assert any( + "TappdClient is deprecated" in msg for msg in warning_messages) + assert any( + "derive_key is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -291,8 +295,10 @@ def test_tappd_client_tdx_quote_deprecated(): client.tdx_quote("test data", "raw") # Should have warnings for both constructor and tdx_quote warning_messages = [str(warning.message) for warning in w] - assert any("TappdClient is deprecated" in msg for msg in warning_messages) - assert any("tdx_quote is deprecated" in msg for msg in warning_messages) + assert any( + "TappdClient is deprecated" in msg for msg in warning_messages) + assert any( + "tdx_quote is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -323,7 +329,8 @@ async def test_async_tappd_client_derive_key_deprecated(): assert any( "AsyncTappdClient is deprecated" in msg for msg in warning_messages ) - assert any("derive_key is deprecated" in msg for msg in warning_messages) + assert any( + "derive_key is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -343,7 +350,8 @@ async def test_async_tappd_client_tdx_quote_deprecated(): assert any( "AsyncTappdClient is deprecated" in msg for msg in warning_messages ) - assert any("tdx_quote is deprecated" in msg for msg in warning_messages) + assert any( + "tdx_quote is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -355,3 +363,42 @@ async def test_async_tappd_client_is_reachable(): client = AsyncTappdClient() is_reachable = await client.is_reachable() assert isinstance(is_reachable, bool) + assert is_reachable + + +# Test sync client called from async context +@pytest.mark.asyncio +async def test_sync_client_in_async_context_get_key(): + """Test that sync client works when called from async context.""" + client = DstackClient() + result = client.get_key() + assert isinstance(result, GetKeyResponse) + assert isinstance(result.decode_key(), bytes) + assert len(result.decode_key()) == 32 + + +@pytest.mark.asyncio +async def test_sync_client_in_async_context_get_info(): + """Test that sync client info works when called from async context.""" + client = DstackClient() + result = client.info() + check_info_response(result) + + +@pytest.mark.asyncio +async def test_mixed_sync_async_calls(): + """Test mixing sync and async client calls in the same async context.""" + sync_client = DstackClient() + async_client = AsyncDstackClient() + + # Call sync client from async context + sync_result = sync_client.get_key() + assert isinstance(sync_result, GetKeyResponse) + + # Call async client normally + async_result = await async_client.get_key() + assert isinstance(async_result, GetKeyResponse) + + # Both should work and return valid results + assert len(sync_result.decode_key()) == 32 + assert len(async_result.decode_key()) == 32 From 215a9686201fc78997218f3d5bf794d0808d2bb2 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Fri, 15 Aug 2025 22:56:12 +0800 Subject: [PATCH 09/20] imp: typing for the new universal client model & share TCP connection in same context. --- sdk/python/Makefile | 6 +- sdk/python/mypy.ini | 14 + sdk/python/pyproject.toml | 6 + sdk/python/src/dstack_sdk/dstack_client.py | 446 ++++++++++---------- sdk/python/src/dstack_sdk/dstack_client.pyi | 143 +++++++ sdk/python/src/dstack_sdk/py.typed | 0 sdk/python/tests/test_client.py | 22 +- sdk/python/tests/test_ethereum.py | 74 +++- sdk/python/tests/test_mypy_check.py | 105 +++++ sdk/python/tests/test_typing.py | 164 +++++++ 10 files changed, 713 insertions(+), 267 deletions(-) create mode 100644 sdk/python/mypy.ini create mode 100644 sdk/python/src/dstack_sdk/dstack_client.pyi create mode 100644 sdk/python/src/dstack_sdk/py.typed create mode 100644 sdk/python/tests/test_mypy_check.py create mode 100644 sdk/python/tests/test_typing.py diff --git a/sdk/python/Makefile b/sdk/python/Makefile index 3d13ec35b..172a83f7b 100644 --- a/sdk/python/Makefile +++ b/sdk/python/Makefile @@ -1,4 +1,4 @@ -.PHONY: install lint format type-check test check-all verify +.PHONY: install lint format type-check type-check-tests test check-all verify # Install dependencies including linting tools install: @@ -19,6 +19,10 @@ lint: type-check: pdm run mypy src/ +# Run type checking on tests specifically +type-check-tests: + pdm run mypy tests/test_mypy_check.py tests/test_typing.py + # Run tests test: @if [ -z "$$DSTACK_SIMULATOR_ENDPOINT" ]; then \ diff --git a/sdk/python/mypy.ini b/sdk/python/mypy.ini new file mode 100644 index 000000000..dbbb8ba10 --- /dev/null +++ b/sdk/python/mypy.ini @@ -0,0 +1,14 @@ +[mypy] +python_version = 3.10 +warn_return_any = True +warn_unused_configs = True +disallow_untyped_defs = False +disallow_incomplete_defs = False +check_untyped_defs = True +disallow_untyped_decorators = False +no_implicit_optional = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_no_return = True +warn_unreachable = True +strict_equality = True diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 0293ec3fc..8a342a5e9 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -142,6 +142,12 @@ test-cov = "pytest --cov=src/" check-all = {composite = ["fmt", "lint", "test"]} verify = {composite = ["check", "test"]} +[tool.pytest.ini_options] +asyncio_default_fixture_loop_scope = "function" +markers = [ + "asyncio: marks tests as asyncio (deselect with '-m \"not asyncio\"')" +] + [tool.pdm.dev-dependencies] solana = [ "solders", diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 271826b14..b09c776ea 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -4,21 +4,27 @@ import base64 import binascii +import functools import hashlib +import inspect import json import logging import os +import warnings +from abc import abstractmethod from typing import Any from typing import Dict from typing import List from typing import Optional +from typing import cast +from typing import get_args +from typing import get_origin import httpx from pydantic import BaseModel logger = logging.getLogger("dstack_sdk") -# SDK version for User-Agent header __version__ = "0.2.0" @@ -26,7 +32,6 @@ def replay_rtmr(history: list[str]) -> str: - """Replay RTMR history and return the final RTMR value as hex string.""" if len(history) == 0: return INIT_MR mr = bytes.fromhex(INIT_MR) @@ -41,11 +46,6 @@ def replay_rtmr(history: list[str]) -> str: def get_endpoint(endpoint: str | None = None) -> str: - """Return the dstack endpoint from argument or environment. - - If ``endpoint`` is not provided, check ``DSTACK_SIMULATOR_ENDPOINT`` in the - environment; otherwise fall back to the default Unix socket path. - """ if endpoint: return endpoint if "DSTACK_SIMULATOR_ENDPOINT" in os.environ: @@ -57,33 +57,23 @@ def get_endpoint(endpoint: str | None = None) -> str: def get_tappd_endpoint(endpoint: str | None = None) -> str: - """Return the tappd endpoint from argument or environment. - - If ``endpoint`` is not provided, check ``TAPPD_SIMULATOR_ENDPOINT`` in the - environment; otherwise fall back to the default Unix socket path. - """ if endpoint: return endpoint if "TAPPD_SIMULATOR_ENDPOINT" in os.environ: - logger.info( - f"Using tappd endpoint: {os.environ['TAPPD_SIMULATOR_ENDPOINT']}") + logger.info(f"Using tappd endpoint: {os.environ['TAPPD_SIMULATOR_ENDPOINT']}") return os.environ["TAPPD_SIMULATOR_ENDPOINT"] return "/var/run/tappd.sock" -class GetTlsKeyResponse(BaseModel): - """Response type for TLS key requests.""" +def emit_deprecation_warning(message: str, stacklevel: int = 2) -> None: + warnings.warn(message, DeprecationWarning, stacklevel=stacklevel) + +class GetTlsKeyResponse(BaseModel): key: str certificate_chain: List[str] def as_uint8array(self, max_length: Optional[int] = None) -> bytes: - """Return the TLS private key as DER bytes. - - If ``max_length`` is provided, the result is padded/truncated to that - length. - """ - # Remove PEM headers and decode content = self.key.replace("-----BEGIN PRIVATE KEY-----", "") content = content.replace("-----END PRIVATE KEY-----", "") content = content.replace("\n", "").replace(" ", "") @@ -93,7 +83,6 @@ def as_uint8array(self, max_length: Optional[int] = None) -> bytes: if max_length is None: return binary_der else: - # Pad or truncate to max_length result = bytearray(max_length) copy_len = min(len(binary_der), max_length) result[:copy_len] = binary_der[:copy_len] @@ -101,36 +90,27 @@ def as_uint8array(self, max_length: Optional[int] = None) -> bytes: class GetKeyResponse(BaseModel): - """Response type for app key derivation requests.""" - key: str signature_chain: List[str] def decode_key(self) -> bytes: - """Decode the hex key into raw bytes.""" return bytes.fromhex(self.key) def decode_signature_chain(self) -> List[bytes]: - """Decode the signature chain entries from hex to bytes.""" return [bytes.fromhex(chain) for chain in self.signature_chain] class GetQuoteResponse(BaseModel): - """Response type for quote requests, including event log.""" - quote: str event_log: str def decode_quote(self) -> bytes: - """Decode the quote from hex to bytes.""" return bytes.fromhex(self.quote) def decode_event_log(self) -> "List[EventLog]": - """Parse the event log JSON into a list of ``EventLog`` objects.""" return [EventLog(**event) for event in json.loads(self.event_log)] def replay_rtmrs(self) -> Dict[int, str]: - """Recompute RTMR registers (0-3) from the event log JSON.""" parsed_event_log = json.loads(self.event_log) rtmrs: Dict[int, str] = {} for idx in range(4): @@ -142,8 +122,6 @@ def replay_rtmrs(self) -> Dict[int, str]: class EventLog(BaseModel): - """Single event entry included in the quote event log.""" - imr: int event_type: int digest: str @@ -152,14 +130,12 @@ class EventLog(BaseModel): class TcbInfo(BaseModel): - """Trusted computing base information returned by the service.""" - mrtd: str rtmr0: str rtmr1: str rtmr2: str rtmr3: str - os_image_hash: str = "" # Optional: empty if OS image is not measured by KMS + os_image_hash: str = "" compose_hash: str device_id: str app_compose: str @@ -167,21 +143,18 @@ class TcbInfo(BaseModel): class InfoResponse(BaseModel): - """Service information response including TCB info and metadata.""" - app_id: str instance_id: str app_cert: str tcb_info: TcbInfo app_name: str device_id: str - os_image_hash: str = "" # Optional: empty if OS image is not measured by KMS + os_image_hash: str = "" key_provider_info: str compose_hash: str @classmethod def parse_response(cls, obj: Any) -> "InfoResponse": - """Parse raw JSON into ``InfoResponse``, decoding nested ``tcb_info``.""" if ( isinstance(obj, dict) and "tcb_info" in obj @@ -192,78 +165,12 @@ def parse_response(cls, obj: Any) -> "InfoResponse": return cls(**obj) -class BaseClient: - """Marker base class for dstack clients.""" - - -class AsyncDstackClient(BaseClient): - """Asynchronous client for dstack services. - - This client can work in both async and sync contexts by using - sync HTTP internally while maintaining async method signatures. - """ - - PATH_PREFIX = "/" - - def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): - """Initialize async client with HTTP or Unix-socket transport. - - Args: - endpoint: HTTP/HTTPS URL or Unix socket path - use_sync_http: If True, use sync HTTP client internally - """ - endpoint = get_endpoint(endpoint) - self.use_sync_http = use_sync_http - - if endpoint.startswith("http://") or endpoint.startswith("https://"): - if use_sync_http: - self.transport = httpx.HTTPTransport() - else: - self.transport = httpx.AsyncHTTPTransport() - self.base_url = endpoint - else: - # Check if Unix socket file exists - if endpoint.startswith("/") and not os.path.exists(endpoint): - raise FileNotFoundError( - f"Unix socket file {endpoint} does not exist") - if use_sync_http: - self.transport = httpx.HTTPTransport(uds=endpoint) - else: - self.transport = httpx.AsyncHTTPTransport(uds=endpoint) - self.base_url = "http://localhost" - +class BusinessMethodsMixin: + @abstractmethod async def _send_rpc_request( self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: - """Send an RPC request and return parsed JSON. - - Uses sync or async HTTP client based on use_sync_http flag. - Maintains async signature for compatibility. - """ - path = self.PATH_PREFIX + method - headers = { - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - } - - if self.use_sync_http: - # Use sync HTTP client - works from any context - with httpx.Client( - transport=self.transport, base_url=self.base_url - ) as client: - response = client.post(path, json=payload, headers=headers) - response.raise_for_status() - from typing import cast - return cast(Dict[str, Any], response.json()) - else: - # Use async HTTP client - traditional async behavior - async with httpx.AsyncClient( - transport=self.transport, base_url=self.base_url - ) as client: - response = await client.post(path, json=payload, headers=headers) - response.raise_for_status() - from typing import cast - return cast(Dict[str, Any], response.json()) + pass async def get_key( self, @@ -342,25 +249,54 @@ async def is_reachable(self) -> bool: return False -class AsyncTappdClient(AsyncDstackClient): - """Deprecated async client kept for backward compatibility. +def sync_version(async_method): - DEPRECATED: Use ``AsyncDstackClient`` instead. - """ - PATH_PREFIX = "/prpc/Tappd." + def _step_coro(coro): + try: + result = coro.send(None) + raise RuntimeError(f"Coroutine yielded unexpected value: {result}") + except StopIteration as e: + return e.value - def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): - """Initialize deprecated async tappd client wrapper.""" - import warnings + @functools.wraps(async_method) + def sync_wrapper(self, *args, **kwargs): + coro = async_method(self, *args, **kwargs) + return _step_coro(coro) - warnings.warn( - "AsyncTappdClient is deprecated, please use AsyncDstackClient instead", - DeprecationWarning, - stacklevel=2, - ) + # Copy annotations but fix the return type for coroutines + sync_wrapper.__annotations__ = async_method.__annotations__.copy() - endpoint = get_tappd_endpoint(endpoint) - super().__init__(endpoint, use_sync_http=use_sync_http) + # Extract the actual return type from Coroutine[Any, Any, T] -> T + if "return" in sync_wrapper.__annotations__: + return_annotation = sync_wrapper.__annotations__["return"] + + # Handle different forms of coroutine annotations + origin = get_origin(return_annotation) + args = get_args(return_annotation) + + # Check for Coroutine[Any, Any, T] pattern + if origin is not None and len(args) >= 3: + # If it's a coroutine type, extract the actual return type (third argument) + actual_return_type = args[2] + sync_wrapper.__annotations__["return"] = actual_return_type + + # Also handle cases where the annotation might be a string + elif isinstance(return_annotation, str) and "Coroutine" in return_annotation: + # For string annotations, we need a different approach + # For now, we'll rely on the runtime type checking + pass + + return sync_wrapper + + +class TappdMethodsMixin: + """Deprecated Tappd methods mixin for backward compatibility.""" + + @abstractmethod + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: + pass async def derive_key( self, @@ -369,13 +305,7 @@ async def derive_key( alt_names: List[str] | None = None, ) -> GetTlsKeyResponse: """Use ``get_key`` instead (deprecated).""" - import warnings - - warnings.warn( - "derive_key is deprecated, please use get_key instead", - DeprecationWarning, - stacklevel=2, - ) + emit_deprecation_warning("derive_key is deprecated, please use get_key instead") data: Dict[str, Any] = { "path": path or "", @@ -393,12 +323,8 @@ async def tdx_quote( hash_algorithm: str | None = None, ) -> GetQuoteResponse: """Use ``get_quote`` instead (deprecated).""" - import warnings - - warnings.warn( - "tdx_quote is deprecated, please use get_quote instead", - DeprecationWarning, - stacklevel=2, + emit_deprecation_warning( + "tdx_quote is deprecated, please use get_quote instead" ) if not report_data or not isinstance(report_data, (bytes, str)): @@ -417,8 +343,7 @@ async def tdx_quote( if len(hex_data) < 128: hex_data = hex_data.zfill(128) - payload = {"report_data": hex_data, - "hash_algorithm": hash_algorithm or "raw"} + payload = {"report_data": hex_data, "hash_algorithm": hash_algorithm or "raw"} result = await self._send_rpc_request("TdxQuote", payload) @@ -428,117 +353,178 @@ async def tdx_quote( return GetQuoteResponse(**result) -def call_async(func): - """Decorator to call async methods synchronously. +class SyncMethodsMixin: - This decorator wraps a method to call its async counterpart from - self.async_client and run it synchronously using asyncio. + pass - Supports being called from within async contexts by using - a sync HTTP client internally and a custom coroutine runner. - """ - import functools - def _step_coro(coro): - """Step through a coroutine that only does sync operations.""" - try: - result = coro.send(None) - raise RuntimeError(f"Coroutine yielded unexpected value: {result}") - except StopIteration as e: - return e.value +class SyncMethodsMeta(type): + + def __new__(mcs, name, bases, namespace, **kwargs): + cls = super().__new__(mcs, name, bases, namespace, **kwargs) + + for base in bases: + if not hasattr(base, "_send_rpc_request"): + continue + + for method_name in dir(base): + if method_name.startswith("_"): + continue + + base_method = getattr(base, method_name, None) + if not callable(base_method): + continue + + cls_method = getattr(cls, method_name, None) + if cls_method and inspect.iscoroutinefunction(cls_method): + sync_method = sync_version(cls_method) - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - async_method = getattr(self.async_client, func.__name__) - return _step_coro(async_method(*args, **kwargs)) - return wrapper + # Fix the return type annotation for mypy + if ( + hasattr(sync_method, "__annotations__") + and "return" in sync_method.__annotations__ + ): + return_annotation = sync_method.__annotations__["return"] + origin = get_origin(return_annotation) + args = get_args(return_annotation) + # Extract return type from Coroutine[Any, Any, T] -> T + if origin is not None and len(args) >= 3: + sync_method.__annotations__["return"] = args[2] -class DstackClient(BaseClient): - """Synchronous client for dstack services.""" + setattr(cls, method_name, sync_method) + + return cls + + +class BaseClient: + pass + + +class AsyncDstackClient(BaseClient, BusinessMethodsMixin): + + PATH_PREFIX = "/" def __init__(self, endpoint: str | None = None): - """Initialize client with HTTP or Unix-socket transport. + endpoint = get_endpoint(endpoint) + self._client: Optional[httpx.AsyncClient] = None - If a non-HTTP(S) endpoint is provided, it is treated as a Unix socket - path and validated for existence. - """ - self.async_client = AsyncDstackClient(endpoint, use_sync_http=True) + if endpoint.startswith("http://") or endpoint.startswith("https://"): + self.transport = httpx.AsyncHTTPTransport() + self.base_url = endpoint + else: + # Check if Unix socket file exists + if endpoint.startswith("/") and not os.path.exists(endpoint): + raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") + self.transport = httpx.AsyncHTTPTransport(uds=endpoint) + self.base_url = "http://localhost" - @call_async - def get_key( - self, - path: str | None = None, - purpose: str | None = None, - ) -> GetKeyResponse: - """Derive a key from the given path and purpose.""" - raise NotImplementedError + def _get_client(self) -> httpx.AsyncClient: + if self._client is None: + self._client = httpx.AsyncClient( + transport=self.transport, base_url=self.base_url + ) + return self._client - @call_async - def get_quote( - self, - report_data: str | bytes, - ) -> GetQuoteResponse: - """Request an attestation quote for the provided report data.""" - raise NotImplementedError + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: + path = self.PATH_PREFIX + method + headers = { + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + } - @call_async - def info(self) -> InfoResponse: - """Fetch service information including parsed TCB info.""" - raise NotImplementedError + client = self._get_client() + response = await client.post(path, json=payload, headers=headers) + response.raise_for_status() + return cast(Dict[str, Any], response.json()) - @call_async - def emit_event( - self, - event: str, - payload: str | bytes, - ) -> None: - """Emit an event that extends RTMR3 on TDX platforms.""" - raise NotImplementedError + async def __aenter__(self): + return self - @call_async - def get_tls_key( - self, - subject: str | None = None, - alt_names: List[str] | None = None, - usage_ra_tls: bool = False, - usage_server_auth: bool = True, - usage_client_auth: bool = False, - ) -> GetTlsKeyResponse: - """Request a TLS key from the service with optional parameters.""" - raise NotImplementedError + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self._client: + await self._client.aclose() + self._client = None - @call_async - def is_reachable(self) -> bool: - """Return True if the service responds to a quick health call.""" - raise NotImplementedError +class DstackClient(BaseClient, BusinessMethodsMixin, metaclass=SyncMethodsMeta): + + PATH_PREFIX = "/" + + def __init__(self, endpoint: str | None = None): + endpoint = get_endpoint(endpoint) + self._client: Optional[httpx.Client] = None + + if endpoint.startswith("http://") or endpoint.startswith("https://"): + self.transport = httpx.HTTPTransport() + self.base_url = endpoint + else: + if endpoint.startswith("/") and not os.path.exists(endpoint): + raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") + self.transport = httpx.HTTPTransport(uds=endpoint) + self.base_url = "http://localhost" + + def _get_client(self) -> httpx.Client: + if self._client is None: + self._client = httpx.Client( + transport=self.transport, base_url=self.base_url + ) + return self._client + + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: + path = self.PATH_PREFIX + method + headers = { + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + } + + client = self._get_client() + response = client.post(path, json=payload, headers=headers) + response.raise_for_status() + return cast(Dict[str, Any], response.json()) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self._client: + self._client.close() + self._client = None + + +class AsyncTappdClient(AsyncDstackClient, TappdMethodsMixin): + """Deprecated async client kept for backward compatibility. + + DEPRECATED: Use ``AsyncDstackClient`` instead. + """ + + def __init__(self, endpoint: str | None = None): + emit_deprecation_warning( + "AsyncTappdClient is deprecated, please use AsyncDstackClient instead" + ) -class TappdClient(DstackClient): + endpoint = get_tappd_endpoint(endpoint) + super().__init__(endpoint) + # Set the correct path prefix for tappd + self.PATH_PREFIX = "/prpc/Tappd." + + +class TappdClient(DstackClient, TappdMethodsMixin, metaclass=SyncMethodsMeta): """Deprecated client kept for backward compatibility. DEPRECATED: Use ``DstackClient`` instead. """ def __init__(self, endpoint: str | None = None): - """Initialize deprecated tappd client wrapper.""" - self.async_client = AsyncTappdClient(endpoint, use_sync_http=True) - - @call_async - def derive_key( - self, - path: str | None = None, - subject: str | None = None, - alt_names: List[str] | None = None, - ) -> GetTlsKeyResponse: - """Use ``get_key`` instead (deprecated).""" - raise NotImplementedError + emit_deprecation_warning( + "TappdClient is deprecated, please use DstackClient instead" + ) - @call_async - def tdx_quote( - self, - report_data: str | bytes, - hash_algorithm: str | None = None, - ) -> GetQuoteResponse: - """Use ``get_quote`` instead (deprecated).""" - raise NotImplementedError + endpoint = get_tappd_endpoint(endpoint) + super().__init__(endpoint) + # Set the correct path prefix for tappd + self.PATH_PREFIX = "/prpc/Tappd." diff --git a/sdk/python/src/dstack_sdk/dstack_client.pyi b/sdk/python/src/dstack_sdk/dstack_client.pyi new file mode 100644 index 000000000..0f5f55572 --- /dev/null +++ b/sdk/python/src/dstack_sdk/dstack_client.pyi @@ -0,0 +1,143 @@ +"""Type stubs for dstack_client module to fix sync method return types.""" + +from abc import abstractmethod +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +class GetTlsKeyResponse: + key: str + certificate_chain: List[str] + def as_uint8array(self, max_length: Optional[int] = ...) -> bytes: ... + +class GetKeyResponse: + key: str + signature_chain: List[str] + def decode_key(self) -> bytes: ... + def decode_signature_chain(self) -> List[bytes]: ... + +class GetQuoteResponse: + quote: str + event_log: str + def decode_quote(self) -> bytes: ... + def decode_event_log(self) -> List[EventLog]: ... + def replay_rtmrs(self) -> Dict[int, str]: ... + +class EventLog: + imr: int + event_type: int + digest: str + event: str + event_payload: str + +class TcbInfo: + mrtd: str + rtmr0: str + rtmr1: str + rtmr2: str + rtmr3: str + os_image_hash: str + compose_hash: str + device_id: str + app_compose: str + event_log: List[EventLog] + +class InfoResponse: + app_id: str + instance_id: str + app_cert: str + tcb_info: TcbInfo + app_name: str + device_id: str + os_image_hash: str + key_provider_info: str + compose_hash: str + @classmethod + def parse_response(cls, obj: Any) -> InfoResponse: ... + +class BusinessMethodsMixin: + @abstractmethod + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: ... + + # Async methods + async def get_key( + self, path: str | None = ..., purpose: str | None = ... + ) -> GetKeyResponse: ... + async def get_quote(self, report_data: str | bytes) -> GetQuoteResponse: ... + async def info(self) -> InfoResponse: ... + async def emit_event(self, event: str, payload: str | bytes) -> None: ... + async def get_tls_key( + self, + subject: str | None = ..., + alt_names: List[str] | None = ..., + usage_ra_tls: bool = ..., + usage_server_auth: bool = ..., + usage_client_auth: bool = ..., + ) -> GetTlsKeyResponse: ... + async def is_reachable(self) -> bool: ... + +class TappdMethodsMixin: + @abstractmethod + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: ... + async def derive_key( + self, + path: str | None = ..., + subject: str | None = ..., + alt_names: List[str] | None = ..., + ) -> GetTlsKeyResponse: ... + async def tdx_quote( + self, report_data: str | bytes, hash_algorithm: str | None = ... + ) -> GetQuoteResponse: ... + +class BaseClient: ... + +class AsyncDstackClient(BaseClient, BusinessMethodsMixin): + def __init__(self, endpoint: str | None = ...) -> None: ... + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: ... + +class DstackClient(BaseClient, BusinessMethodsMixin): + def __init__(self, endpoint: str | None = ...) -> None: ... + async def _send_rpc_request( + self, method: str, payload: Dict[str, Any] + ) -> Dict[str, Any]: ... + + # Sync methods - these should NOT return Coroutine + def get_key( + self, path: str | None = ..., purpose: str | None = ... + ) -> GetKeyResponse: ... + def get_quote(self, report_data: str | bytes) -> GetQuoteResponse: ... + def info(self) -> InfoResponse: ... + def emit_event(self, event: str, payload: str | bytes) -> None: ... + def get_tls_key( + self, + subject: str | None = ..., + alt_names: List[str] | None = ..., + usage_ra_tls: bool = ..., + usage_server_auth: bool = ..., + usage_client_auth: bool = ..., + ) -> GetTlsKeyResponse: ... + def is_reachable(self) -> bool: ... + +class AsyncTappdClient(AsyncDstackClient, TappdMethodsMixin): + def __init__(self, endpoint: str | None = ...) -> None: ... + +class TappdClient(DstackClient, TappdMethodsMixin): + def __init__(self, endpoint: str | None = ...) -> None: ... + + # Sync deprecated methods + def derive_key( + self, + path: str | None = ..., + subject: str | None = ..., + alt_names: List[str] | None = ..., + ) -> GetTlsKeyResponse: ... + def tdx_quote( + self, report_data: str | bytes, hash_algorithm: str | None = ... + ) -> GetQuoteResponse: ... diff --git a/sdk/python/src/dstack_sdk/py.typed b/sdk/python/src/dstack_sdk/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 9e9f47cf5..9eb0f5d4d 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -107,7 +107,7 @@ async def test_replay_rtmr(): client = AsyncDstackClient() result = await client.get_quote("test") # TODO evidence_api is a bit out-of-date, we need an up-to-date implementation. - tdxQuote = TdxQuote(bytes.fromhex(result.quote)) + tdxQuote = TdxQuote(bytearray(bytes.fromhex(result.quote))) rtmrs = result.replay_rtmrs() assert rtmrs[0] == tdxQuote.body.rtmr0.hex() assert rtmrs[1] == tdxQuote.body.rtmr1.hex() @@ -132,7 +132,7 @@ async def test_report_data(): reportdata = "test" client = AsyncDstackClient() result = await client.get_quote(reportdata) - tdxQuote = TdxQuote(result.decode_quote()) + tdxQuote = TdxQuote(bytearray(result.decode_quote())) reportdata = reportdata.encode("utf-8") + b"\x00" * (64 - len(reportdata)) assert reportdata == tdxQuote.body.reportdata @@ -276,10 +276,8 @@ def test_tappd_client_derive_key_deprecated(): client.derive_key("/", "test") # Should have warnings for both constructor and derive_key warning_messages = [str(warning.message) for warning in w] - assert any( - "TappdClient is deprecated" in msg for msg in warning_messages) - assert any( - "derive_key is deprecated" in msg for msg in warning_messages) + assert any("TappdClient is deprecated" in msg for msg in warning_messages) + assert any("derive_key is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -295,10 +293,8 @@ def test_tappd_client_tdx_quote_deprecated(): client.tdx_quote("test data", "raw") # Should have warnings for both constructor and tdx_quote warning_messages = [str(warning.message) for warning in w] - assert any( - "TappdClient is deprecated" in msg for msg in warning_messages) - assert any( - "tdx_quote is deprecated" in msg for msg in warning_messages) + assert any("TappdClient is deprecated" in msg for msg in warning_messages) + assert any("tdx_quote is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -329,8 +325,7 @@ async def test_async_tappd_client_derive_key_deprecated(): assert any( "AsyncTappdClient is deprecated" in msg for msg in warning_messages ) - assert any( - "derive_key is deprecated" in msg for msg in warning_messages) + assert any("derive_key is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass @@ -350,8 +345,7 @@ async def test_async_tappd_client_tdx_quote_deprecated(): assert any( "AsyncTappdClient is deprecated" in msg for msg in warning_messages ) - assert any( - "tdx_quote is deprecated" in msg for msg in warning_messages) + assert any("tdx_quote is deprecated" in msg for msg in warning_messages) except Exception: # It's OK if this fails due to simulator not supporting the old endpoint pass diff --git a/sdk/python/tests/test_ethereum.py b/sdk/python/tests/test_ethereum.py index 35ab4a463..7d8d3a70e 100644 --- a/sdk/python/tests/test_ethereum.py +++ b/sdk/python/tests/test_ethereum.py @@ -14,46 +14,67 @@ @pytest.mark.asyncio async def test_async_to_account(): - client = AsyncDstackClient() - result = await client.get_key("test") - assert isinstance(result, GetKeyResponse) - account = to_account(result) + # Use mock GetKeyResponse instead of actual server call + mock_result = GetKeyResponse( + key="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + signature_chain=["sig1", "sig2"], + ) + assert isinstance(mock_result, GetKeyResponse) + account = to_account(mock_result) assert isinstance(account, LocalAccount) def test_sync_to_account(): - client = DstackClient() - result = client.get_key("test") - assert isinstance(result, GetKeyResponse) - account = to_account(result) + # Use mock GetKeyResponse instead of actual server call + mock_result = GetKeyResponse( + key="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + signature_chain=["sig1", "sig2"], + ) + assert isinstance(mock_result, GetKeyResponse) + account = to_account(mock_result) assert isinstance(account, LocalAccount) @pytest.mark.asyncio async def test_async_to_account_secure(): - client = AsyncDstackClient() - result = await client.get_key("test") - assert isinstance(result, GetKeyResponse) - account = to_account_secure(result) + # Use mock GetKeyResponse instead of actual server call + mock_result = GetKeyResponse( + key="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + signature_chain=["sig1", "sig2"], + ) + assert isinstance(mock_result, GetKeyResponse) + account = to_account_secure(mock_result) assert isinstance(account, LocalAccount) def test_sync_to_account_secure(): - client = DstackClient() - result = client.get_key("test") - assert isinstance(result, GetKeyResponse) - account = to_account_secure(result) + # Use mock GetKeyResponse instead of actual server call + mock_result = GetKeyResponse( + key="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + signature_chain=["sig1", "sig2"], + ) + assert isinstance(mock_result, GetKeyResponse) + account = to_account_secure(mock_result) assert isinstance(account, LocalAccount) def test_to_account_with_tls_key(): """Test to_account with TLS key response (should show warning).""" - client = DstackClient() - result = client.get_tls_key() + from dstack_sdk import GetTlsKeyResponse + + # Use mock TLS key response instead of actual server call + mock_result = GetTlsKeyResponse( + key="""-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKONKWRjMvhgxHDmr +SY7zfjPHe3Qp8vCO9HqjzjqhXNKhRANCAAT5XHKyj7JRGHl2nQ2SltGKjQ3A7MPJ +/7JDkUxMNYhTxKqYdJZ6l1C8XrjKc7SFsVJhYgdJjLzQ3xKJz6l5jKzQ +-----END PRIVATE KEY-----""", + certificate_chain=["cert1", "cert2"], + ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") - account = to_account(result) + account = to_account(mock_result) assert isinstance(account, LocalAccount) assert len(w) == 1 @@ -63,12 +84,21 @@ def test_to_account_with_tls_key(): def test_to_account_secure_with_tls_key(): """Test to_account_secure with TLS key response (should show warning).""" - client = DstackClient() - result = client.get_tls_key() + from dstack_sdk import GetTlsKeyResponse + + # Use mock TLS key response instead of actual server call + mock_result = GetTlsKeyResponse( + key="""-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKONKWRjMvhgxHDmr +SY7zfjPHe3Qp8vCO9HqjzjqhXNKhRANCAAT5XHKyj7JRGHl2nQ2SltGKjQ3A7MPJ +/7JDkUxMNYhTxKqYdJZ6l1C8XrjKc7SFsVJhYgdJjLzQ3xKJz6l5jKzQ +-----END PRIVATE KEY-----""", + certificate_chain=["cert1", "cert2"], + ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") - account = to_account_secure(result) + account = to_account_secure(mock_result) assert isinstance(account, LocalAccount) assert len(w) == 1 diff --git a/sdk/python/tests/test_mypy_check.py b/sdk/python/tests/test_mypy_check.py new file mode 100644 index 000000000..6bacd25d5 --- /dev/null +++ b/sdk/python/tests/test_mypy_check.py @@ -0,0 +1,105 @@ +"""Script to test mypy type checking on dstack client methods.""" + +import os +from unittest.mock import Mock +from unittest.mock import patch + +try: + from typing_extensions import reveal_type +except ImportError: + from typing import reveal_type # type: ignore + +import pytest + +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient +from dstack_sdk import GetKeyResponse +from dstack_sdk import GetTlsKeyResponse + + +def test_sync_client_types(): + """Test sync client method type inference with mypy.""" + # Use the simulator endpoint if available, otherwise use a mock + endpoint = os.environ.get("DSTACK_SIMULATOR_ENDPOINT", "http://localhost:8080") + + with patch("httpx.Client.post") as mock_post: + # Mock the HTTP response for the tests + mock_response = Mock() + mock_response.status_code = 200 + + # Mock response for get_tls_key + mock_response.json.return_value = { + "key": "test_key", + "certificate_chain": ["test_cert_1", "test_cert_2"], + } + mock_post.return_value = mock_response + + client = DstackClient(endpoint) + + # Test get_tls_key - this should be GetTlsKeyResponse, not Coroutine + tls_result = client.get_tls_key() + reveal_type(tls_result) # Should be GetTlsKeyResponse + + # Mock response for get_key + mock_response.json.return_value = { + "key": "test_key_hex", + "signature_chain": ["sig1", "sig2"], + } + + # Test get_key + key_result = client.get_key() + reveal_type(key_result) # Should be GetKeyResponse + + # Mock response for is_reachable + mock_response.json.return_value = True + + # Test is_reachable + reachable = client.is_reachable() + reveal_type(reachable) # Should be bool + + +@pytest.mark.asyncio +async def test_async_client_types(): + """Test async client method type inference with mypy.""" + # Use the simulator endpoint if available, otherwise use a mock + endpoint = os.environ.get("DSTACK_SIMULATOR_ENDPOINT", "http://localhost:8080") + + with patch("httpx.AsyncClient.post") as mock_post: + # Mock the HTTP response for the tests + mock_response = Mock() + mock_response.status_code = 200 + + # Mock response for get_tls_key + mock_response.json.return_value = { + "key": "test_key", + "certificate_chain": ["test_cert_1", "test_cert_2"], + } + mock_post.return_value = mock_response + + client = AsyncDstackClient(endpoint) + + # Test get_tls_key - this should be GetTlsKeyResponse + tls_result = await client.get_tls_key() + reveal_type(tls_result) # Should be GetTlsKeyResponse + + # Mock response for get_key + mock_response.json.return_value = { + "key": "test_key_hex", + "signature_chain": ["sig1", "sig2"], + } + + # Test get_key + key_result = await client.get_key() + reveal_type(key_result) # Should be GetKeyResponse + + # Mock response for is_reachable + mock_response.json.return_value = True + + # Test is_reachable + reachable = await client.is_reachable() + reveal_type(reachable) # Should be bool + + +if __name__ == "__main__": + # This script is meant to be checked with mypy, not executed + pass diff --git a/sdk/python/tests/test_typing.py b/sdk/python/tests/test_typing.py new file mode 100644 index 000000000..690acf84c --- /dev/null +++ b/sdk/python/tests/test_typing.py @@ -0,0 +1,164 @@ +"""Test typing and mypy compatibility for sync/async methods.""" + +import inspect +import os +from typing import get_type_hints + +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient +from dstack_sdk import GetKeyResponse +from dstack_sdk import GetQuoteResponse +from dstack_sdk import GetTlsKeyResponse +from dstack_sdk.dstack_client import InfoResponse + +# Use a test endpoint to avoid socket file not found errors +TEST_ENDPOINT = "http://localhost:8080" + + +def test_sync_method_type_annotations(): + """Test that sync methods have correct type annotations, not Coroutine.""" + client = DstackClient(TEST_ENDPOINT) + + # Check get_tls_key method + get_tls_key_method = getattr(client, "get_tls_key") + assert callable(get_tls_key_method) + + # Get type hints for the method + try: + type_hints = get_type_hints(get_tls_key_method) + print(f"get_tls_key type hints: {type_hints}") + + # The return type should be GetTlsKeyResponse, not Coroutine + return_type = type_hints.get("return", None) + print(f"get_tls_key return type: {return_type}") + + # This should pass - return type should be GetTlsKeyResponse + assert return_type == GetTlsKeyResponse or return_type is GetTlsKeyResponse + + except Exception as e: + print(f"Error getting type hints for get_tls_key: {e}") + # If we can't get type hints, at least check it's not a coroutine function + assert not inspect.iscoroutinefunction( + get_tls_key_method + ), "Sync method should not be a coroutine function" + + +def test_all_sync_method_types(): + """Test all sync business methods have correct type annotations.""" + client = DstackClient(TEST_ENDPOINT) + + expected_types = { + "get_key": GetKeyResponse, + "get_quote": GetQuoteResponse, + "get_tls_key": GetTlsKeyResponse, + "info": InfoResponse, + "emit_event": type(None), # Returns None + "is_reachable": bool, + } + + for method_name, expected_return_type in expected_types.items(): + method = getattr(client, method_name) + assert callable(method), f"{method_name} should be callable" + + # Should not be coroutine function for sync client + assert not inspect.iscoroutinefunction( + method + ), f"Sync {method_name} should not be coroutine function" + + try: + type_hints = get_type_hints(method) + return_type = type_hints.get("return", None) + print( + f"{method_name} return type: {return_type}, expected: {expected_return_type}" + ) + + # Check that return type matches expected + if expected_return_type is not None: + assert ( + return_type == expected_return_type + or return_type is expected_return_type + ), f"{method_name} should return {expected_return_type}, got {return_type}" + + except Exception as e: + print(f"Warning: Could not get type hints for {method_name}: {e}") + + +def test_async_method_types(): + """Test that async methods have correct type annotations.""" + client = AsyncDstackClient(TEST_ENDPOINT) + + expected_types = { + "get_key": GetKeyResponse, + "get_quote": GetQuoteResponse, + "get_tls_key": GetTlsKeyResponse, + "info": InfoResponse, + "emit_event": type(None), + "is_reachable": bool, + } + + for method_name, expected_return_type in expected_types.items(): + method = getattr(client, method_name) + assert callable(method), f"{method_name} should be callable" + + # Should be coroutine function for async client + assert inspect.iscoroutinefunction( + method + ), f"Async {method_name} should be coroutine function" + + try: + type_hints = get_type_hints(method) + return_type = type_hints.get("return", None) + print( + f"Async {method_name} return type: {return_type}, expected: {expected_return_type}" + ) + + except Exception as e: + print(f"Warning: Could not get type hints for async {method_name}: {e}") + + +def test_method_signature_comparison(): + """Compare method signatures between sync and async versions.""" + sync_client = DstackClient(TEST_ENDPOINT) + async_client = AsyncDstackClient(TEST_ENDPOINT) + + methods_to_check = [ + "get_key", + "get_quote", + "get_tls_key", + "info", + "emit_event", + "is_reachable", + ] + + for method_name in methods_to_check: + sync_method = getattr(sync_client, method_name) + async_method = getattr(async_client, method_name) + + sync_sig = inspect.signature(sync_method) + async_sig = inspect.signature(async_method) + + print(f"\n{method_name}:") + print(f" Sync signature: {sync_sig}") + print(f" Async signature: {async_sig}") + + # Parameters should be the same (excluding 'self') + sync_params = list(sync_sig.parameters.keys()) + async_params = list(async_sig.parameters.keys()) + + if "self" in sync_params: + sync_params.remove("self") + if "self" in async_params: + async_params.remove("self") + + assert ( + sync_params == async_params + ), f"Parameter mismatch for {method_name}: sync={sync_params}, async={async_params}" + + +if __name__ == "__main__": + # Run the tests manually to see output + test_sync_method_type_annotations() + test_all_sync_method_types() + test_async_method_types() + test_method_signature_comparison() + print("All typing tests completed!") From 8579545e19ae61586dd93b3a9381bbbeb7da4b4d Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Fri, 15 Aug 2025 22:59:25 +0800 Subject: [PATCH 10/20] imp: set default timeout for api client. --- sdk/python/src/dstack_sdk/dstack_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index b09c776ea..71b50467e 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -422,7 +422,7 @@ def __init__(self, endpoint: str | None = None): def _get_client(self) -> httpx.AsyncClient: if self._client is None: self._client = httpx.AsyncClient( - transport=self.transport, base_url=self.base_url + transport=self.transport, base_url=self.base_url, timeout=0.5 ) return self._client @@ -469,7 +469,7 @@ def __init__(self, endpoint: str | None = None): def _get_client(self) -> httpx.Client: if self._client is None: self._client = httpx.Client( - transport=self.transport, base_url=self.base_url + transport=self.transport, base_url=self.base_url, timeout=0.5 ) return self._client From 98fd72125cc772dda893ca3a69099cc7f7254d4f Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Fri, 15 Aug 2025 23:32:55 +0800 Subject: [PATCH 11/20] fix: typing & errors after fix conflict --- sdk/python/pyproject.toml | 1 + sdk/python/src/dstack_sdk/dstack_client.pyi | 68 +++++++++++++++------ sdk/python/src/dstack_sdk/ethereum.py | 5 +- sdk/python/src/dstack_sdk/solana.py | 1 + sdk/python/tests/test_client.py | 2 + sdk/python/tests/test_ethereum.py | 4 +- sdk/python/tests/test_mypy_check.py | 2 - sdk/python/tests/test_solana.py | 2 + sdk/python/tests/test_typing.py | 1 - 9 files changed, 59 insertions(+), 27 deletions(-) diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 8a342a5e9..bd93a5cc1 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -118,6 +118,7 @@ exclude = [ per-file-ignores = [ "__init__.py:F401", # imported but unused "tests/*.py:D100,D101,D102,D103,D104", # relax docstring rules for tests + "src/dstack_sdk/*.py:D100,D101,D102,D103,D104,D105,D106,D107", # relax all docstring rules for source code ] [tool.pdm] distribution = true diff --git a/sdk/python/src/dstack_sdk/dstack_client.pyi b/sdk/python/src/dstack_sdk/dstack_client.pyi index 0f5f55572..a953066d4 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.pyi +++ b/sdk/python/src/dstack_sdk/dstack_client.pyi @@ -6,32 +6,34 @@ from typing import Dict from typing import List from typing import Optional -class GetTlsKeyResponse: +from pydantic import BaseModel + +class GetTlsKeyResponse(BaseModel): key: str certificate_chain: List[str] def as_uint8array(self, max_length: Optional[int] = ...) -> bytes: ... -class GetKeyResponse: +class GetKeyResponse(BaseModel): key: str signature_chain: List[str] def decode_key(self) -> bytes: ... def decode_signature_chain(self) -> List[bytes]: ... -class GetQuoteResponse: +class GetQuoteResponse(BaseModel): quote: str event_log: str def decode_quote(self) -> bytes: ... def decode_event_log(self) -> List[EventLog]: ... def replay_rtmrs(self) -> Dict[int, str]: ... -class EventLog: +class EventLog(BaseModel): imr: int event_type: int digest: str event: str event_payload: str -class TcbInfo: +class TcbInfo(BaseModel): mrtd: str rtmr0: str rtmr1: str @@ -43,7 +45,7 @@ class TcbInfo: app_compose: str event_log: List[EventLog] -class InfoResponse: +class InfoResponse(BaseModel): app_id: str instance_id: str app_cert: str @@ -62,37 +64,35 @@ class BusinessMethodsMixin: self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: ... - # Async methods - async def get_key( - self, path: str | None = ..., purpose: str | None = ... - ) -> GetKeyResponse: ... - async def get_quote(self, report_data: str | bytes) -> GetQuoteResponse: ... - async def info(self) -> InfoResponse: ... - async def emit_event(self, event: str, payload: str | bytes) -> None: ... - async def get_tls_key( + # Methods can be either async or sync depending on implementation + def get_key(self, path: str | None = ..., purpose: str | None = ...) -> Any: ... + def get_quote(self, report_data: str | bytes) -> Any: ... + def info(self) -> Any: ... + def emit_event(self, event: str, payload: str | bytes) -> Any: ... + def get_tls_key( self, subject: str | None = ..., alt_names: List[str] | None = ..., usage_ra_tls: bool = ..., usage_server_auth: bool = ..., usage_client_auth: bool = ..., - ) -> GetTlsKeyResponse: ... - async def is_reachable(self) -> bool: ... + ) -> Any: ... + def is_reachable(self) -> Any: ... class TappdMethodsMixin: @abstractmethod async def _send_rpc_request( self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: ... - async def derive_key( + def derive_key( self, path: str | None = ..., subject: str | None = ..., alt_names: List[str] | None = ..., - ) -> GetTlsKeyResponse: ... - async def tdx_quote( + ) -> Any: ... + def tdx_quote( self, report_data: str | bytes, hash_algorithm: str | None = ... - ) -> GetQuoteResponse: ... + ) -> Any: ... class BaseClient: ... @@ -102,6 +102,23 @@ class AsyncDstackClient(BaseClient, BusinessMethodsMixin): self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: ... + # Override with specific async return types + async def get_key( + self, path: str | None = ..., purpose: str | None = ... + ) -> GetKeyResponse: ... + async def get_quote(self, report_data: str | bytes) -> GetQuoteResponse: ... + async def info(self) -> InfoResponse: ... + async def emit_event(self, event: str, payload: str | bytes) -> None: ... + async def get_tls_key( + self, + subject: str | None = ..., + alt_names: List[str] | None = ..., + usage_ra_tls: bool = ..., + usage_server_auth: bool = ..., + usage_client_auth: bool = ..., + ) -> GetTlsKeyResponse: ... + async def is_reachable(self) -> bool: ... + class DstackClient(BaseClient, BusinessMethodsMixin): def __init__(self, endpoint: str | None = ...) -> None: ... async def _send_rpc_request( @@ -128,6 +145,17 @@ class DstackClient(BaseClient, BusinessMethodsMixin): class AsyncTappdClient(AsyncDstackClient, TappdMethodsMixin): def __init__(self, endpoint: str | None = ...) -> None: ... + # Override with specific async return types + async def derive_key( + self, + path: str | None = ..., + subject: str | None = ..., + alt_names: List[str] | None = ..., + ) -> GetTlsKeyResponse: ... + async def tdx_quote( + self, report_data: str | bytes, hash_algorithm: str | None = ... + ) -> GetQuoteResponse: ... + class TappdClient(DstackClient, TappdMethodsMixin): def __init__(self, endpoint: str | None = ...) -> None: ... diff --git a/sdk/python/src/dstack_sdk/ethereum.py b/sdk/python/src/dstack_sdk/ethereum.py index 0d18b9619..b4b1cdc2a 100644 --- a/sdk/python/src/dstack_sdk/ethereum.py +++ b/sdk/python/src/dstack_sdk/ethereum.py @@ -7,6 +7,7 @@ Use with ``dstack_sdk.DstackClient`` responses to create ``eth_account`` objects for signing and transacting. """ +import hashlib import warnings from eth_account import Account @@ -29,7 +30,7 @@ def to_account(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> LocalAcc Returns: Account: Ethereum account object """ - if hasattr(get_key_response, "as_uint8array"): # GetTlsKeyResponse + if isinstance(get_key_response, GetTlsKeyResponse): warnings.warn( "to_account: Please don't use getTlsKey method to get key, use getKey instead.", DeprecationWarning, @@ -45,7 +46,7 @@ def to_account_secure( get_key_response: GetKeyResponse | GetTlsKeyResponse, ) -> LocalAccount: """Create an Ethereum account using SHA256 of full key material for security.""" - if hasattr(get_key_response, "as_uint8array"): # GetTlsKeyResponse + if isinstance(get_key_response, GetTlsKeyResponse): warnings.warn( "to_account_secure: Please don't use getTlsKey method to get key, use getKey instead.", DeprecationWarning, diff --git a/sdk/python/src/dstack_sdk/solana.py b/sdk/python/src/dstack_sdk/solana.py index c8506bf2c..a4e4ed4a4 100644 --- a/sdk/python/src/dstack_sdk/solana.py +++ b/sdk/python/src/dstack_sdk/solana.py @@ -7,6 +7,7 @@ Use with ``dstack_sdk.DstackClient`` responses to create ``solders.Keypair`` objects for signing transactions on Solana. """ +import hashlib import warnings from solders.keypair import Keypair diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 9eb0f5d4d..70fd78121 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 +import warnings + import pytest from evidence_api.tdx.quote import TdxQuote diff --git a/sdk/python/tests/test_ethereum.py b/sdk/python/tests/test_ethereum.py index 7d8d3a70e..2bf748acb 100644 --- a/sdk/python/tests/test_ethereum.py +++ b/sdk/python/tests/test_ethereum.py @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 +import warnings + import pytest from eth_account.signers.local import LocalAccount -from dstack_sdk import AsyncDstackClient -from dstack_sdk import DstackClient from dstack_sdk import GetKeyResponse from dstack_sdk.ethereum import to_account from dstack_sdk.ethereum import to_account_secure diff --git a/sdk/python/tests/test_mypy_check.py b/sdk/python/tests/test_mypy_check.py index 6bacd25d5..0c7871b59 100644 --- a/sdk/python/tests/test_mypy_check.py +++ b/sdk/python/tests/test_mypy_check.py @@ -13,8 +13,6 @@ from dstack_sdk import AsyncDstackClient from dstack_sdk import DstackClient -from dstack_sdk import GetKeyResponse -from dstack_sdk import GetTlsKeyResponse def test_sync_client_types(): diff --git a/sdk/python/tests/test_solana.py b/sdk/python/tests/test_solana.py index 6e42794e8..b263184fd 100644 --- a/sdk/python/tests/test_solana.py +++ b/sdk/python/tests/test_solana.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 +import warnings + import pytest from solders.keypair import Keypair diff --git a/sdk/python/tests/test_typing.py b/sdk/python/tests/test_typing.py index 690acf84c..197438d52 100644 --- a/sdk/python/tests/test_typing.py +++ b/sdk/python/tests/test_typing.py @@ -1,7 +1,6 @@ """Test typing and mypy compatibility for sync/async methods.""" import inspect -import os from typing import get_type_hints from dstack_sdk import AsyncDstackClient From 3ad0da1908ce459f0cdbdc4cd04554193849845e Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Fri, 15 Aug 2025 23:35:42 +0800 Subject: [PATCH 12/20] chore: SPDX --- sdk/js/test-outputs.js | 3 +++ sdk/python/Makefile | 4 ++++ sdk/python/mypy.ini | 4 ++++ sdk/python/src/dstack_sdk/dstack_client.pyi | 4 ++++ sdk/python/src/dstack_sdk/encrypt_env_vars.py | 4 ++++ sdk/python/src/dstack_sdk/get_compose_hash.py | 4 ++++ sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py | 4 ++++ sdk/python/test_outputs.py | 4 +++- sdk/python/tests/test_client.py | 2 +- sdk/python/tests/test_encrypt_env_vars.py | 4 ++++ sdk/python/tests/test_get_compose_hash.py | 4 ++++ sdk/python/tests/test_mypy_check.py | 4 ++++ sdk/python/tests/test_typing.py | 4 ++++ sdk/python/tests/test_verify_env_encrypt_public_key.py | 4 ++++ 14 files changed, 51 insertions(+), 2 deletions(-) diff --git a/sdk/js/test-outputs.js b/sdk/js/test-outputs.js index 5d22878ae..6c791319f 100644 --- a/sdk/js/test-outputs.js +++ b/sdk/js/test-outputs.js @@ -1,4 +1,7 @@ #!/usr/bin/env node +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 const { DstackClient, TappdClient, getComposeHash, verifyEnvEncryptPublicKey } = require('./dist/node/index.js'); const { toViemAccount, toViemAccountSecure } = require('./dist/node/viem.js'); diff --git a/sdk/python/Makefile b/sdk/python/Makefile index 172a83f7b..88d462327 100644 --- a/sdk/python/Makefile +++ b/sdk/python/Makefile @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + .PHONY: install lint format type-check type-check-tests test check-all verify # Install dependencies including linting tools diff --git a/sdk/python/mypy.ini b/sdk/python/mypy.ini index dbbb8ba10..c05c83ea4 100644 --- a/sdk/python/mypy.ini +++ b/sdk/python/mypy.ini @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + [mypy] python_version = 3.10 warn_return_any = True diff --git a/sdk/python/src/dstack_sdk/dstack_client.pyi b/sdk/python/src/dstack_sdk/dstack_client.pyi index a953066d4..df534f065 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.pyi +++ b/sdk/python/src/dstack_sdk/dstack_client.pyi @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + """Type stubs for dstack_client module to fix sync method return types.""" from abc import abstractmethod diff --git a/sdk/python/src/dstack_sdk/encrypt_env_vars.py b/sdk/python/src/dstack_sdk/encrypt_env_vars.py index 685e9e2eb..41f0da875 100644 --- a/sdk/python/src/dstack_sdk/encrypt_env_vars.py +++ b/sdk/python/src/dstack_sdk/encrypt_env_vars.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + """ Environment variable encryption module for dstack SDK. diff --git a/sdk/python/src/dstack_sdk/get_compose_hash.py b/sdk/python/src/dstack_sdk/get_compose_hash.py index 683b3b90b..b31cc0ade 100644 --- a/sdk/python/src/dstack_sdk/get_compose_hash.py +++ b/sdk/python/src/dstack_sdk/get_compose_hash.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + """ Compose hash calculation module for dstack SDK. diff --git a/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py b/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py index 120d37dc3..2c432f294 100644 --- a/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py +++ b/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + """Verify ECDSA signatures of environment-encrypt public keys. This module prepares the message per dstack convention and offers a simplified diff --git a/sdk/python/test_outputs.py b/sdk/python/test_outputs.py index ad2074c6e..5ae96a138 100644 --- a/sdk/python/test_outputs.py +++ b/sdk/python/test_outputs.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python3 +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 import asyncio import sys diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 70fd78121..b76e423e8 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2024-2025 Phala Network +# SPDX-FileCopyrightText: © 2025 Phala Network # # SPDX-License-Identifier: Apache-2.0 diff --git a/sdk/python/tests/test_encrypt_env_vars.py b/sdk/python/tests/test_encrypt_env_vars.py index cb662e7ac..d50bd51ca 100644 --- a/sdk/python/tests/test_encrypt_env_vars.py +++ b/sdk/python/tests/test_encrypt_env_vars.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + import pytest from dstack_sdk.encrypt_env_vars import EnvVar diff --git a/sdk/python/tests/test_get_compose_hash.py b/sdk/python/tests/test_get_compose_hash.py index 4a4559260..50ea9f0a4 100644 --- a/sdk/python/tests/test_get_compose_hash.py +++ b/sdk/python/tests/test_get_compose_hash.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + from dstack_sdk.get_compose_hash import AppCompose from dstack_sdk.get_compose_hash import DockerConfig from dstack_sdk.get_compose_hash import get_compose_hash diff --git a/sdk/python/tests/test_mypy_check.py b/sdk/python/tests/test_mypy_check.py index 0c7871b59..4067bf424 100644 --- a/sdk/python/tests/test_mypy_check.py +++ b/sdk/python/tests/test_mypy_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + """Script to test mypy type checking on dstack client methods.""" import os diff --git a/sdk/python/tests/test_typing.py b/sdk/python/tests/test_typing.py index 197438d52..5c635dda1 100644 --- a/sdk/python/tests/test_typing.py +++ b/sdk/python/tests/test_typing.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + """Test typing and mypy compatibility for sync/async methods.""" import inspect diff --git a/sdk/python/tests/test_verify_env_encrypt_public_key.py b/sdk/python/tests/test_verify_env_encrypt_public_key.py index 78c9c0f57..002a0a3f2 100644 --- a/sdk/python/tests/test_verify_env_encrypt_public_key.py +++ b/sdk/python/tests/test_verify_env_encrypt_public_key.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + from dstack_sdk.verify_env_encrypt_public_key import verify_env_encrypt_public_key from dstack_sdk.verify_env_encrypt_public_key import verify_signature_simple From 3220ce6f9237db193904a4cdc38df407f5d59394 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Sat, 16 Aug 2025 14:06:30 +0800 Subject: [PATCH 13/20] refactor: back to decorator --- sdk/python/src/dstack_sdk/dstack_client.py | 467 ++++++++++-------- sdk/python/src/dstack_sdk/dstack_client.pyi | 175 ------- sdk/python/tests/test_connection_reuse.py | 202 ++++++++ .../tests/test_tcp_connection_validation.py | 173 +++++++ 4 files changed, 634 insertions(+), 383 deletions(-) delete mode 100644 sdk/python/src/dstack_sdk/dstack_client.pyi create mode 100644 sdk/python/tests/test_connection_reuse.py create mode 100644 sdk/python/tests/test_tcp_connection_validation.py diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 71b50467e..c202e3a8f 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -6,19 +6,15 @@ import binascii import functools import hashlib -import inspect import json import logging import os import warnings -from abc import abstractmethod from typing import Any from typing import Dict from typing import List from typing import Optional from typing import cast -from typing import get_args -from typing import get_origin import httpx from pydantic import BaseModel @@ -69,6 +65,58 @@ def emit_deprecation_warning(message: str, stacklevel: int = 2) -> None: warnings.warn(message, DeprecationWarning, stacklevel=stacklevel) +def call_async(func): + """Call async methods synchronously. + + This decorator wraps a method to call its async counterpart from + self.async_client and run it synchronously using asyncio. + + Supports being called from within async contexts by using + a sync HTTP client internally and a custom coroutine runner. + """ + + def _step_coro(coro): + """Step through a coroutine that only does sync operations.""" + try: + result = coro.send(None) + raise RuntimeError(f"Coroutine yielded unexpected value: {result}") + except StopIteration as e: + return e.value + + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + async_method = getattr(self.async_client, func.__name__) + return _step_coro(async_method(*args, **kwargs)) + + return wrapper + + +def call_async_with_deprecation(method_name, deprecation_message): + """Create decorator for deprecated methods that call async implementations.""" + + def decorator(func): + def _step_coro(coro): + try: + result = coro.send(None) + raise RuntimeError(f"Coroutine yielded unexpected value: {result}") + except StopIteration as e: + return e.value + + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + emit_deprecation_warning(deprecation_message) + if hasattr(self.async_client, method_name): + async_method = getattr(self.async_client, method_name) + return _step_coro(async_method(*args, **kwargs)) + else: + # Fallback for methods not in async_client + return _step_coro(func(self, *args, **kwargs)) + + return wrapper + + return decorator + + class GetTlsKeyResponse(BaseModel): key: str certificate_chain: List[str] @@ -165,12 +213,98 @@ def parse_response(cls, obj: Any) -> "InfoResponse": return cls(**obj) -class BusinessMethodsMixin: - @abstractmethod +class BaseClient: + pass + + +class AsyncDstackClient(BaseClient): + + PATH_PREFIX = "/" + + def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): + """Initialize async client with HTTP or Unix-socket transport. + + Args: + endpoint: HTTP/HTTPS URL or Unix socket path + use_sync_http: If True, use sync HTTP client internally + """ + endpoint = get_endpoint(endpoint) + self.use_sync_http = use_sync_http + self._client: Optional[httpx.AsyncClient] = None + self._sync_client: Optional[httpx.Client] = None + self._client_ref_count = 0 + + if endpoint.startswith("http://") or endpoint.startswith("https://"): + self.async_transport = httpx.AsyncHTTPTransport() + self.sync_transport = httpx.HTTPTransport() + self.base_url = endpoint + else: + # Check if Unix socket file exists + if endpoint.startswith("/") and not os.path.exists(endpoint): + raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") + self.async_transport = httpx.AsyncHTTPTransport(uds=endpoint) + self.sync_transport = httpx.HTTPTransport(uds=endpoint) + self.base_url = "http://localhost" + + def _get_client(self) -> httpx.AsyncClient: + if self._client is None: + self._client = httpx.AsyncClient( + transport=self.async_transport, base_url=self.base_url, timeout=0.5 + ) + return self._client + + def _get_sync_client(self) -> httpx.Client: + if self._sync_client is None: + self._sync_client = httpx.Client( + transport=self.sync_transport, base_url=self.base_url, timeout=0.5 + ) + return self._sync_client + async def _send_rpc_request( self, method: str, payload: Dict[str, Any] ) -> Dict[str, Any]: - pass + """Send an RPC request and return parsed JSON. + + Uses sync or async HTTP client based on use_sync_http flag. + Maintains async signature for compatibility. + """ + path = self.PATH_PREFIX + method + headers = { + "Content-Type": "application/json", + "User-Agent": f"dstack-sdk-python/{__version__}", + } + + if self.use_sync_http: + # Use sync HTTP client - works from any context + sync_client: httpx.Client = self._get_sync_client() + response = sync_client.post(path, json=payload, headers=headers) + response.raise_for_status() + return cast(Dict[str, Any], response.json()) + else: + # Use async HTTP client - traditional async behavior + async_client: httpx.AsyncClient = self._get_client() + response = await async_client.post(path, json=payload, headers=headers) + response.raise_for_status() + return cast(Dict[str, Any], response.json()) + + async def __aenter__(self): + self._client_ref_count += 1 + # Eagerly create client when entering context + if self.use_sync_http: + self._get_sync_client() + else: + self._get_client() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self._client_ref_count -= 1 + if self._client_ref_count == 0: + if self._client: + await self._client.aclose() + self._client = None + if self._sync_client: + self._sync_client.close() + self._sync_client = None async def get_key( self, @@ -249,54 +383,103 @@ async def is_reachable(self) -> bool: return False -def sync_version(async_method): +class DstackClient(BaseClient): - def _step_coro(coro): - try: - result = coro.send(None) - raise RuntimeError(f"Coroutine yielded unexpected value: {result}") - except StopIteration as e: - return e.value + PATH_PREFIX = "/" + + def __init__(self, endpoint: str | None = None): + """Initialize client with HTTP or Unix-socket transport. + + If a non-HTTP(S) endpoint is provided, it is treated as a Unix socket + path and validated for existence. + """ + self.async_client = AsyncDstackClient(endpoint, use_sync_http=True) + + @call_async + def get_key( + self, + path: str | None = None, + purpose: str | None = None, + ) -> GetKeyResponse: + """Derive a key from the given path and purpose.""" + raise NotImplementedError + + @call_async + def get_quote( + self, + report_data: str | bytes, + ) -> GetQuoteResponse: + """Request an attestation quote for the provided report data.""" + raise NotImplementedError - @functools.wraps(async_method) - def sync_wrapper(self, *args, **kwargs): - coro = async_method(self, *args, **kwargs) - return _step_coro(coro) + @call_async + def info(self) -> InfoResponse: + """Fetch service information including parsed TCB info.""" + raise NotImplementedError + + @call_async + def emit_event( + self, + event: str, + payload: str | bytes, + ) -> None: + """Emit an event that extends RTMR3 on TDX platforms.""" + raise NotImplementedError - # Copy annotations but fix the return type for coroutines - sync_wrapper.__annotations__ = async_method.__annotations__.copy() + @call_async + def get_tls_key( + self, + subject: str | None = None, + alt_names: List[str] | None = None, + usage_ra_tls: bool = False, + usage_server_auth: bool = True, + usage_client_auth: bool = False, + ) -> GetTlsKeyResponse: + """Request a TLS key from the service with optional parameters.""" + raise NotImplementedError - # Extract the actual return type from Coroutine[Any, Any, T] -> T - if "return" in sync_wrapper.__annotations__: - return_annotation = sync_wrapper.__annotations__["return"] + @call_async + def is_reachable(self) -> bool: + """Return True if the service responds to a quick health call.""" + raise NotImplementedError - # Handle different forms of coroutine annotations - origin = get_origin(return_annotation) - args = get_args(return_annotation) + def __enter__(self): + # Delegate to async client's context management for proper ref counting + self.async_client._client_ref_count += 1 + # Create sync client eagerly for sync context manager + if self.async_client.use_sync_http: + self.async_client._get_sync_client() + else: + self.async_client._get_client() + return self - # Check for Coroutine[Any, Any, T] pattern - if origin is not None and len(args) >= 3: - # If it's a coroutine type, extract the actual return type (third argument) - actual_return_type = args[2] - sync_wrapper.__annotations__["return"] = actual_return_type + def __exit__(self, exc_type, exc_val, exc_tb): + # Delegate to async client's context management for proper cleanup + self.async_client._client_ref_count -= 1 + if self.async_client._client_ref_count == 0: + # For sync context, we only clean up sync clients + # Async clients should not be created in sync context anyway + if self.async_client._sync_client: + self.async_client._sync_client.close() + self.async_client._sync_client = None - # Also handle cases where the annotation might be a string - elif isinstance(return_annotation, str) and "Coroutine" in return_annotation: - # For string annotations, we need a different approach - # For now, we'll rely on the runtime type checking - pass - return sync_wrapper +class AsyncTappdClient(AsyncDstackClient): + """Deprecated async client kept for backward compatibility. + DEPRECATED: Use ``AsyncDstackClient`` instead. + """ -class TappdMethodsMixin: - """Deprecated Tappd methods mixin for backward compatibility.""" + def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): + """Initialize deprecated async tappd client wrapper.""" + emit_deprecation_warning( + "AsyncTappdClient is deprecated, please use AsyncDstackClient instead" + ) - @abstractmethod - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: - pass + endpoint = get_tappd_endpoint(endpoint) + super().__init__(endpoint, use_sync_http=use_sync_http) + # Set the correct path prefix for tappd + self.PATH_PREFIX = "/prpc/Tappd." async def derive_key( self, @@ -353,178 +536,46 @@ async def tdx_quote( return GetQuoteResponse(**result) -class SyncMethodsMixin: - - pass - - -class SyncMethodsMeta(type): - - def __new__(mcs, name, bases, namespace, **kwargs): - cls = super().__new__(mcs, name, bases, namespace, **kwargs) - - for base in bases: - if not hasattr(base, "_send_rpc_request"): - continue - - for method_name in dir(base): - if method_name.startswith("_"): - continue - - base_method = getattr(base, method_name, None) - if not callable(base_method): - continue - - cls_method = getattr(cls, method_name, None) - if cls_method and inspect.iscoroutinefunction(cls_method): - sync_method = sync_version(cls_method) - - # Fix the return type annotation for mypy - if ( - hasattr(sync_method, "__annotations__") - and "return" in sync_method.__annotations__ - ): - return_annotation = sync_method.__annotations__["return"] - origin = get_origin(return_annotation) - args = get_args(return_annotation) - - # Extract return type from Coroutine[Any, Any, T] -> T - if origin is not None and len(args) >= 3: - sync_method.__annotations__["return"] = args[2] - - setattr(cls, method_name, sync_method) - - return cls - - -class BaseClient: - pass - - -class AsyncDstackClient(BaseClient, BusinessMethodsMixin): - - PATH_PREFIX = "/" - - def __init__(self, endpoint: str | None = None): - endpoint = get_endpoint(endpoint) - self._client: Optional[httpx.AsyncClient] = None - - if endpoint.startswith("http://") or endpoint.startswith("https://"): - self.transport = httpx.AsyncHTTPTransport() - self.base_url = endpoint - else: - # Check if Unix socket file exists - if endpoint.startswith("/") and not os.path.exists(endpoint): - raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") - self.transport = httpx.AsyncHTTPTransport(uds=endpoint) - self.base_url = "http://localhost" - - def _get_client(self) -> httpx.AsyncClient: - if self._client is None: - self._client = httpx.AsyncClient( - transport=self.transport, base_url=self.base_url, timeout=0.5 - ) - return self._client - - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: - path = self.PATH_PREFIX + method - headers = { - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - } - - client = self._get_client() - response = await client.post(path, json=payload, headers=headers) - response.raise_for_status() - return cast(Dict[str, Any], response.json()) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - if self._client: - await self._client.aclose() - self._client = None - - -class DstackClient(BaseClient, BusinessMethodsMixin, metaclass=SyncMethodsMeta): - - PATH_PREFIX = "/" - - def __init__(self, endpoint: str | None = None): - endpoint = get_endpoint(endpoint) - self._client: Optional[httpx.Client] = None - - if endpoint.startswith("http://") or endpoint.startswith("https://"): - self.transport = httpx.HTTPTransport() - self.base_url = endpoint - else: - if endpoint.startswith("/") and not os.path.exists(endpoint): - raise FileNotFoundError(f"Unix socket file {endpoint} does not exist") - self.transport = httpx.HTTPTransport(uds=endpoint) - self.base_url = "http://localhost" - - def _get_client(self) -> httpx.Client: - if self._client is None: - self._client = httpx.Client( - transport=self.transport, base_url=self.base_url, timeout=0.5 - ) - return self._client - - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: - path = self.PATH_PREFIX + method - headers = { - "Content-Type": "application/json", - "User-Agent": f"dstack-sdk-python/{__version__}", - } - - client = self._get_client() - response = client.post(path, json=payload, headers=headers) - response.raise_for_status() - return cast(Dict[str, Any], response.json()) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if self._client: - self._client.close() - self._client = None - - -class AsyncTappdClient(AsyncDstackClient, TappdMethodsMixin): - """Deprecated async client kept for backward compatibility. +class TappdClient(DstackClient): + """Deprecated client kept for backward compatibility. - DEPRECATED: Use ``AsyncDstackClient`` instead. + DEPRECATED: Use ``DstackClient`` instead. """ def __init__(self, endpoint: str | None = None): + """Initialize deprecated tappd client wrapper.""" emit_deprecation_warning( - "AsyncTappdClient is deprecated, please use AsyncDstackClient instead" + "TappdClient is deprecated, please use DstackClient instead" ) - endpoint = get_tappd_endpoint(endpoint) - super().__init__(endpoint) - # Set the correct path prefix for tappd - self.PATH_PREFIX = "/prpc/Tappd." + self.async_client = AsyncDstackClient(endpoint, use_sync_http=True) + self.async_client.PATH_PREFIX = "/prpc/Tappd." + @call_async_with_deprecation( + "derive_key", "derive_key is deprecated, please use get_key instead" + ) + def derive_key( + self, + path: str | None = None, + subject: str | None = None, + alt_names: List[str] | None = None, + ) -> GetTlsKeyResponse: + """Use ``get_key`` instead (deprecated).""" + raise NotImplementedError -class TappdClient(DstackClient, TappdMethodsMixin, metaclass=SyncMethodsMeta): - """Deprecated client kept for backward compatibility. - - DEPRECATED: Use ``DstackClient`` instead. - """ + @call_async_with_deprecation( + "tdx_quote", "tdx_quote is deprecated, please use get_quote instead" + ) + def tdx_quote( + self, + report_data: str | bytes, + hash_algorithm: str | None = None, + ) -> GetQuoteResponse: + """Use ``get_quote`` instead (deprecated).""" + raise NotImplementedError - def __init__(self, endpoint: str | None = None): - emit_deprecation_warning( - "TappdClient is deprecated, please use DstackClient instead" - ) + def __enter__(self): + return self - endpoint = get_tappd_endpoint(endpoint) - super().__init__(endpoint) - # Set the correct path prefix for tappd - self.PATH_PREFIX = "/prpc/Tappd." + def __exit__(self, exc_type, exc_val, exc_tb): + pass diff --git a/sdk/python/src/dstack_sdk/dstack_client.pyi b/sdk/python/src/dstack_sdk/dstack_client.pyi deleted file mode 100644 index df534f065..000000000 --- a/sdk/python/src/dstack_sdk/dstack_client.pyi +++ /dev/null @@ -1,175 +0,0 @@ -# SPDX-FileCopyrightText: © 2025 Phala Network -# -# SPDX-License-Identifier: Apache-2.0 - -"""Type stubs for dstack_client module to fix sync method return types.""" - -from abc import abstractmethod -from typing import Any -from typing import Dict -from typing import List -from typing import Optional - -from pydantic import BaseModel - -class GetTlsKeyResponse(BaseModel): - key: str - certificate_chain: List[str] - def as_uint8array(self, max_length: Optional[int] = ...) -> bytes: ... - -class GetKeyResponse(BaseModel): - key: str - signature_chain: List[str] - def decode_key(self) -> bytes: ... - def decode_signature_chain(self) -> List[bytes]: ... - -class GetQuoteResponse(BaseModel): - quote: str - event_log: str - def decode_quote(self) -> bytes: ... - def decode_event_log(self) -> List[EventLog]: ... - def replay_rtmrs(self) -> Dict[int, str]: ... - -class EventLog(BaseModel): - imr: int - event_type: int - digest: str - event: str - event_payload: str - -class TcbInfo(BaseModel): - mrtd: str - rtmr0: str - rtmr1: str - rtmr2: str - rtmr3: str - os_image_hash: str - compose_hash: str - device_id: str - app_compose: str - event_log: List[EventLog] - -class InfoResponse(BaseModel): - app_id: str - instance_id: str - app_cert: str - tcb_info: TcbInfo - app_name: str - device_id: str - os_image_hash: str - key_provider_info: str - compose_hash: str - @classmethod - def parse_response(cls, obj: Any) -> InfoResponse: ... - -class BusinessMethodsMixin: - @abstractmethod - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: ... - - # Methods can be either async or sync depending on implementation - def get_key(self, path: str | None = ..., purpose: str | None = ...) -> Any: ... - def get_quote(self, report_data: str | bytes) -> Any: ... - def info(self) -> Any: ... - def emit_event(self, event: str, payload: str | bytes) -> Any: ... - def get_tls_key( - self, - subject: str | None = ..., - alt_names: List[str] | None = ..., - usage_ra_tls: bool = ..., - usage_server_auth: bool = ..., - usage_client_auth: bool = ..., - ) -> Any: ... - def is_reachable(self) -> Any: ... - -class TappdMethodsMixin: - @abstractmethod - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: ... - def derive_key( - self, - path: str | None = ..., - subject: str | None = ..., - alt_names: List[str] | None = ..., - ) -> Any: ... - def tdx_quote( - self, report_data: str | bytes, hash_algorithm: str | None = ... - ) -> Any: ... - -class BaseClient: ... - -class AsyncDstackClient(BaseClient, BusinessMethodsMixin): - def __init__(self, endpoint: str | None = ...) -> None: ... - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: ... - - # Override with specific async return types - async def get_key( - self, path: str | None = ..., purpose: str | None = ... - ) -> GetKeyResponse: ... - async def get_quote(self, report_data: str | bytes) -> GetQuoteResponse: ... - async def info(self) -> InfoResponse: ... - async def emit_event(self, event: str, payload: str | bytes) -> None: ... - async def get_tls_key( - self, - subject: str | None = ..., - alt_names: List[str] | None = ..., - usage_ra_tls: bool = ..., - usage_server_auth: bool = ..., - usage_client_auth: bool = ..., - ) -> GetTlsKeyResponse: ... - async def is_reachable(self) -> bool: ... - -class DstackClient(BaseClient, BusinessMethodsMixin): - def __init__(self, endpoint: str | None = ...) -> None: ... - async def _send_rpc_request( - self, method: str, payload: Dict[str, Any] - ) -> Dict[str, Any]: ... - - # Sync methods - these should NOT return Coroutine - def get_key( - self, path: str | None = ..., purpose: str | None = ... - ) -> GetKeyResponse: ... - def get_quote(self, report_data: str | bytes) -> GetQuoteResponse: ... - def info(self) -> InfoResponse: ... - def emit_event(self, event: str, payload: str | bytes) -> None: ... - def get_tls_key( - self, - subject: str | None = ..., - alt_names: List[str] | None = ..., - usage_ra_tls: bool = ..., - usage_server_auth: bool = ..., - usage_client_auth: bool = ..., - ) -> GetTlsKeyResponse: ... - def is_reachable(self) -> bool: ... - -class AsyncTappdClient(AsyncDstackClient, TappdMethodsMixin): - def __init__(self, endpoint: str | None = ...) -> None: ... - - # Override with specific async return types - async def derive_key( - self, - path: str | None = ..., - subject: str | None = ..., - alt_names: List[str] | None = ..., - ) -> GetTlsKeyResponse: ... - async def tdx_quote( - self, report_data: str | bytes, hash_algorithm: str | None = ... - ) -> GetQuoteResponse: ... - -class TappdClient(DstackClient, TappdMethodsMixin): - def __init__(self, endpoint: str | None = ...) -> None: ... - - # Sync deprecated methods - def derive_key( - self, - path: str | None = ..., - subject: str | None = ..., - alt_names: List[str] | None = ..., - ) -> GetTlsKeyResponse: ... - def tdx_quote( - self, report_data: str | bytes, hash_algorithm: str | None = ... - ) -> GetQuoteResponse: ... diff --git a/sdk/python/tests/test_connection_reuse.py b/sdk/python/tests/test_connection_reuse.py new file mode 100644 index 000000000..73799828a --- /dev/null +++ b/sdk/python/tests/test_connection_reuse.py @@ -0,0 +1,202 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + +import unittest.mock + +import pytest + +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient + + +class TestConnectionReuse: + """Test TCP connection reuse functionality.""" + + @pytest.mark.asyncio + async def test_async_context_manager_reuses_client(self): + """Test that async context manager creates and reuses a single client.""" + client = AsyncDstackClient() + + # Verify client is None initially + assert client._client is None + + async with client: + # Verify client is created when entering context + assert client._client is not None + first_client = client._client + + # Make multiple calls and verify same client is used + with unittest.mock.patch.object(client._client, "post") as mock_post: + # Mock successful response + mock_response = unittest.mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"key": "test", "signature_chain": []} + mock_post.return_value = mock_response + + # Make first call + await client.get_key("test1") + # Make second call + await client.get_key("test2") + + # Verify same client instance was used for both calls + assert client._client is first_client + assert mock_post.call_count == 2 + + # Verify client is cleaned up after exiting context + assert client._client is None + + def test_sync_context_manager_reuses_client(self): + """Test that sync context manager creates and reuses a single client.""" + client = DstackClient() + + # Verify sync client is None initially + assert client.async_client._sync_client is None + + with client: + # Verify client is created when entering context + assert client.async_client._sync_client is not None + first_client = client.async_client._sync_client + + # Make multiple calls and verify same client is used + with unittest.mock.patch.object( + client.async_client._sync_client, "post" + ) as mock_post: + # Mock successful response + mock_response = unittest.mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"key": "test", "signature_chain": []} + mock_post.return_value = mock_response + + # Make first call + client.get_key("test1") + # Make second call + client.get_key("test2") + + # Verify same client instance was used for both calls + assert client.async_client._sync_client is first_client + assert mock_post.call_count == 2 + + # Verify client is cleaned up after exiting context + assert client.async_client._sync_client is None + + @pytest.mark.asyncio + async def test_async_without_context_manager_reuses_client(self): + """Test that without context manager, clients are still reused.""" + client = AsyncDstackClient() + + with unittest.mock.patch("httpx.AsyncClient") as mock_async_client_class: + # Mock the context manager behavior + mock_client_instance = unittest.mock.AsyncMock() + mock_response = unittest.mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"key": "test", "signature_chain": []} + mock_client_instance.post = unittest.mock.AsyncMock( + return_value=mock_response + ) + mock_async_client_class.return_value = mock_client_instance + + # Make two calls + await client.get_key("test1") + await client.get_key("test2") + + # Verify that the client was created and reused (not created twice) + assert mock_async_client_class.call_count == 1 + + def test_sync_without_context_manager_reuses_client(self): + """Test that without context manager, clients are still reused.""" + client = DstackClient() + + with unittest.mock.patch("httpx.Client") as mock_client_class: + # Mock the context manager behavior + mock_client_instance = unittest.mock.Mock() + mock_response = unittest.mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"key": "test", "signature_chain": []} + mock_client_instance.post.return_value = mock_response + mock_client_class.return_value = mock_client_instance + + # Make two calls + client.get_key("test1") + client.get_key("test2") + + # Verify that the client was created and reused (not created twice) + assert mock_client_class.call_count == 1 + + @pytest.mark.asyncio + async def test_async_context_manager_with_real_requests(self): + """Test async context manager with real requests to ensure connection reuse.""" + client = AsyncDstackClient() + + async with client: + # Make multiple requests - these should reuse the same connection + try: + result1 = await client.info() + result2 = await client.info() + + # Both calls should succeed (assuming simulator is running) + assert result1 is not None + assert result2 is not None + assert hasattr(result1, "app_id") + assert hasattr(result2, "app_id") + except Exception: + # If simulator is not running, that's expected + # The important thing is that the context manager worked + pass + + def test_sync_context_manager_with_real_requests(self): + """Test sync context manager with real requests to ensure connection reuse.""" + client = DstackClient() + + with client: + # Make multiple requests - these should reuse the same connection + try: + result1 = client.info() + result2 = client.info() + + # Both calls should succeed (assuming simulator is running) + assert result1 is not None + assert result2 is not None + assert hasattr(result1, "app_id") + assert hasattr(result2, "app_id") + except Exception: + # If simulator is not running, that's expected + # The important thing is that the context manager worked + pass + + @pytest.mark.asyncio + async def test_async_nested_context_managers(self): + """Test that nested async context managers work correctly.""" + client = AsyncDstackClient() + + async with client: + first_client = client._client + assert first_client is not None + + # Nested context should use the same client + async with client: + assert client._client is first_client + + # After nested exit, client should still be active + assert client._client is first_client + + # After outer exit, client should be None + assert client._client is None + + def test_sync_nested_context_managers(self): + """Test that nested sync context managers work correctly.""" + client = DstackClient() + + with client: + first_client = client.async_client._sync_client + assert first_client is not None + + # Nested context should use the same client + with client: + assert client.async_client._sync_client is first_client + + # After nested exit, client should still be active + assert client.async_client._sync_client is first_client + + # After outer exit, client should be None + assert client.async_client._sync_client is None diff --git a/sdk/python/tests/test_tcp_connection_validation.py b/sdk/python/tests/test_tcp_connection_validation.py new file mode 100644 index 000000000..c77e59c1e --- /dev/null +++ b/sdk/python/tests/test_tcp_connection_validation.py @@ -0,0 +1,173 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + +import unittest.mock + +import pytest + +from dstack_sdk import AsyncDstackClient +from dstack_sdk import DstackClient + + +class TestTCPConnectionValidation: + """Test actual TCP connection behavior.""" + + @pytest.mark.asyncio + async def test_async_client_connection_object_reuse(self): + """Test that the actual httpx client object is reused in async context manager.""" + client = AsyncDstackClient() + + async with client: + first_client_obj = client._client + assert first_client_obj is not None + + # Verify client object has expected attributes + assert hasattr(first_client_obj, "post") + assert hasattr(first_client_obj, "_transport") + + # Make a request to establish connection state + with unittest.mock.patch.object(first_client_obj, "post") as mock_post: + mock_response = unittest.mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"key": "test", "signature_chain": []} + mock_post.return_value = mock_response + + await client.get_key("test1") + + # Verify the same client object is still being used + assert client._client is first_client_obj + + await client.get_key("test2") + + # Still the same client object + assert client._client is first_client_obj + assert mock_post.call_count == 2 + + def test_sync_client_connection_object_reuse(self): + """Test that the actual httpx client object is reused in sync context manager.""" + client = DstackClient() + + with client: + # For sync clients, check _sync_client instead of _client + first_client_obj = client.async_client._sync_client + assert first_client_obj is not None + + # Verify client object has expected attributes + assert hasattr(first_client_obj, "post") + assert hasattr(first_client_obj, "_transport") + + # Make a request to establish connection state + with unittest.mock.patch.object(first_client_obj, "post") as mock_post: + mock_response = unittest.mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"key": "test", "signature_chain": []} + mock_post.return_value = mock_response + + client.get_key("test1") + + # Verify the same client object is still being used + assert client.async_client._sync_client is first_client_obj + + client.get_key("test2") + + # Still the same client object + assert client.async_client._sync_client is first_client_obj + assert mock_post.call_count == 2 + + @pytest.mark.asyncio + async def test_async_transport_configuration_preserved(self): + """Test that transport configuration is preserved when using context manager.""" + # Test with HTTP endpoint + http_client = AsyncDstackClient(endpoint="http://localhost:8080") + + async with http_client: + assert http_client._client is not None + assert isinstance( + http_client._client._transport, type(http_client.async_transport) + ) + assert http_client._client.base_url == "http://localhost:8080" + + def test_sync_transport_configuration_preserved(self): + """Test that transport configuration is preserved when using context manager.""" + # Test with HTTP endpoint + http_client = DstackClient(endpoint="http://localhost:8080") + + with http_client: + # For sync clients, check _sync_client instead of _client + assert http_client.async_client._sync_client is not None + assert isinstance( + http_client.async_client._sync_client._transport, + type(http_client.async_client.sync_transport), + ) + assert ( + http_client.async_client._sync_client.base_url + == "http://localhost:8080" + ) + + @pytest.mark.asyncio + async def test_reference_counting_behavior(self): + """Test that reference counting works correctly for nested contexts.""" + client = AsyncDstackClient() + + # Initially no client and ref count is 0 + assert client._client is None + assert client._client_ref_count == 0 + + async with client: # ref_count = 1 + assert client._client_ref_count == 1 + first_client = client._client + assert first_client is not None + + async with client: # ref_count = 2 + assert client._client_ref_count == 2 + assert client._client is first_client # Same client + + async with client: # ref_count = 3 + assert client._client_ref_count == 3 + assert client._client is first_client # Still same client + # ref_count = 2 + assert client._client_ref_count == 2 + assert client._client is first_client # Client still alive + # ref_count = 1 + assert client._client_ref_count == 1 + assert client._client is first_client # Client still alive + # ref_count = 0 + assert client._client_ref_count == 0 + assert client._client is None # Client closed + + def test_sync_reference_counting_behavior(self): + """Test that reference counting works correctly for nested sync contexts.""" + client = DstackClient() + + # Initially no client and ref count is 0 + assert client.async_client._sync_client is None + assert client.async_client._client_ref_count == 0 + + with client: # ref_count = 1 + assert client.async_client._client_ref_count == 1 + first_client = client.async_client._sync_client + assert first_client is not None + + with client: # ref_count = 2 + assert client.async_client._client_ref_count == 2 + assert client.async_client._sync_client is first_client # Same client + + with client: # ref_count = 3 + assert client.async_client._client_ref_count == 3 + assert ( + client.async_client._sync_client is first_client + ) # Still same client + # ref_count = 2 + assert client.async_client._client_ref_count == 2 + assert ( + client.async_client._sync_client is first_client + ) # Client still alive + # ref_count = 1 + assert client.async_client._client_ref_count == 1 + assert ( + client.async_client._sync_client is first_client + ) # Client still alive + # ref_count = 0 + assert client.async_client._client_ref_count == 0 + assert client.async_client._sync_client is None # Client closed From 0dd1c6695d14bfaddf4835a5b00eafe5680e6176 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Sat, 16 Aug 2025 14:25:05 +0800 Subject: [PATCH 14/20] fix: more accurately test for deprecated warning count --- sdk/python/tests/test_client.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index b76e423e8..e90207b15 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -263,9 +263,16 @@ def test_tappd_client_deprecated(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") TappdClient() - assert len(w) == 1 - assert issubclass(w[0].category, DeprecationWarning) - assert "TappdClient is deprecated" in str(w[0].message) + + # Filter for TappdClient deprecation warnings specifically + tappd_warnings = [ + warning for warning in w + if issubclass(warning.category, DeprecationWarning) + and "TappdClient is deprecated" in str(warning.message) + ] + + assert len(tappd_warnings) == 1 + assert "TappdClient is deprecated" in str(tappd_warnings[0].message) def test_tappd_client_derive_key_deprecated(): @@ -308,9 +315,16 @@ def test_async_tappd_client_deprecated(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") AsyncTappdClient() - assert len(w) == 1 - assert issubclass(w[0].category, DeprecationWarning) - assert "AsyncTappdClient is deprecated" in str(w[0].message) + + # Filter for AsyncTappdClient deprecation warnings specifically + tappd_warnings = [ + warning for warning in w + if issubclass(warning.category, DeprecationWarning) + and "AsyncTappdClient is deprecated" in str(warning.message) + ] + + assert len(tappd_warnings) == 1 + assert "AsyncTappdClient is deprecated" in str(tappd_warnings[0].message) @pytest.mark.asyncio From ca96a3661e3a00cfbcd048ff044d629654b12759 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 18 Aug 2025 01:50:57 +0000 Subject: [PATCH 15/20] sdk/python: Remove unecessary call_async_with_deprecation --- sdk/python/src/dstack_sdk/dstack_client.py | 47 +++------------- sdk/python/tests/test_client.py | 64 ++++++++-------------- 2 files changed, 33 insertions(+), 78 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index c202e3a8f..9c21220cb 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -69,7 +69,7 @@ def call_async(func): """Call async methods synchronously. This decorator wraps a method to call its async counterpart from - self.async_client and run it synchronously using asyncio. + self.async_client and run it synchronously using `coro.send(None)`. Supports being called from within async contexts by using a sync HTTP client internally and a custom coroutine runner. @@ -91,32 +91,6 @@ def wrapper(self, *args, **kwargs): return wrapper -def call_async_with_deprecation(method_name, deprecation_message): - """Create decorator for deprecated methods that call async implementations.""" - - def decorator(func): - def _step_coro(coro): - try: - result = coro.send(None) - raise RuntimeError(f"Coroutine yielded unexpected value: {result}") - except StopIteration as e: - return e.value - - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - emit_deprecation_warning(deprecation_message) - if hasattr(self.async_client, method_name): - async_method = getattr(self.async_client, method_name) - return _step_coro(async_method(*args, **kwargs)) - else: - # Fallback for methods not in async_client - return _step_coro(func(self, *args, **kwargs)) - - return wrapper - - return decorator - - class GetTlsKeyResponse(BaseModel): key: str certificate_chain: List[str] @@ -472,9 +446,11 @@ class AsyncTappdClient(AsyncDstackClient): def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): """Initialize deprecated async tappd client wrapper.""" - emit_deprecation_warning( - "AsyncTappdClient is deprecated, please use AsyncDstackClient instead" - ) + if not use_sync_http: + # Already warned in TappdClient.__init__ + emit_deprecation_warning( + "AsyncTappdClient is deprecated, please use AsyncDstackClient instead" + ) endpoint = get_tappd_endpoint(endpoint) super().__init__(endpoint, use_sync_http=use_sync_http) @@ -548,12 +524,9 @@ def __init__(self, endpoint: str | None = None): "TappdClient is deprecated, please use DstackClient instead" ) endpoint = get_tappd_endpoint(endpoint) - self.async_client = AsyncDstackClient(endpoint, use_sync_http=True) - self.async_client.PATH_PREFIX = "/prpc/Tappd." + self.async_client = AsyncTappdClient(endpoint, use_sync_http=True) - @call_async_with_deprecation( - "derive_key", "derive_key is deprecated, please use get_key instead" - ) + @call_async def derive_key( self, path: str | None = None, @@ -563,9 +536,7 @@ def derive_key( """Use ``get_key`` instead (deprecated).""" raise NotImplementedError - @call_async_with_deprecation( - "tdx_quote", "tdx_quote is deprecated, please use get_quote instead" - ) + @call_async def tdx_quote( self, report_data: str | bytes, diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index e90207b15..dc8e71c4a 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -281,15 +281,11 @@ def test_tappd_client_derive_key_deprecated(): warnings.simplefilter("always") client = TappdClient() - try: - client.derive_key("/", "test") - # Should have warnings for both constructor and derive_key - warning_messages = [str(warning.message) for warning in w] - assert any("TappdClient is deprecated" in msg for msg in warning_messages) - assert any("derive_key is deprecated" in msg for msg in warning_messages) - except Exception: - # It's OK if this fails due to simulator not supporting the old endpoint - pass + client.derive_key("/", "test") + # Should have warnings for both constructor and derive_key + warning_messages = [str(warning.message) for warning in w] + assert any("TappdClient is deprecated" in msg for msg in warning_messages) + assert any("derive_key is deprecated" in msg for msg in warning_messages) def test_tappd_client_tdx_quote_deprecated(): @@ -298,15 +294,11 @@ def test_tappd_client_tdx_quote_deprecated(): warnings.simplefilter("always") client = TappdClient() - try: - client.tdx_quote("test data", "raw") - # Should have warnings for both constructor and tdx_quote - warning_messages = [str(warning.message) for warning in w] - assert any("TappdClient is deprecated" in msg for msg in warning_messages) - assert any("tdx_quote is deprecated" in msg for msg in warning_messages) - except Exception: - # It's OK if this fails due to simulator not supporting the old endpoint - pass + client.tdx_quote("test data", "raw") + # Should have warnings for both constructor and tdx_quote + warning_messages = [str(warning.message) for warning in w] + assert any("TappdClient is deprecated" in msg for msg in warning_messages) + assert any("tdx_quote is deprecated" in msg for msg in warning_messages) # Test AsyncTappdClient @@ -334,17 +326,13 @@ async def test_async_tappd_client_derive_key_deprecated(): warnings.simplefilter("always") client = AsyncTappdClient() - try: - await client.derive_key("/", "test") - # Should have warnings for both constructor and derive_key - warning_messages = [str(warning.message) for warning in w] - assert any( - "AsyncTappdClient is deprecated" in msg for msg in warning_messages - ) - assert any("derive_key is deprecated" in msg for msg in warning_messages) - except Exception: - # It's OK if this fails due to simulator not supporting the old endpoint - pass + await client.derive_key("/", "test") + # Should have warnings for both constructor and derive_key + warning_messages = [str(warning.message) for warning in w] + assert any( + "AsyncTappdClient is deprecated" in msg for msg in warning_messages + ) + assert any("derive_key is deprecated" in msg for msg in warning_messages) @pytest.mark.asyncio @@ -354,17 +342,13 @@ async def test_async_tappd_client_tdx_quote_deprecated(): warnings.simplefilter("always") client = AsyncTappdClient() - try: - await client.tdx_quote("test data", "raw") - # Should have warnings for both constructor and tdx_quote - warning_messages = [str(warning.message) for warning in w] - assert any( - "AsyncTappdClient is deprecated" in msg for msg in warning_messages - ) - assert any("tdx_quote is deprecated" in msg for msg in warning_messages) - except Exception: - # It's OK if this fails due to simulator not supporting the old endpoint - pass + await client.tdx_quote("test data", "raw") + # Should have warnings for both constructor and tdx_quote + warning_messages = [str(warning.message) for warning in w] + assert any( + "AsyncTappdClient is deprecated" in msg for msg in warning_messages + ) + assert any("tdx_quote is deprecated" in msg for msg in warning_messages) @pytest.mark.asyncio From 50d81228c444e5de9ef6dba6d474a5ef94c72e67 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 18 Aug 2025 01:55:18 +0000 Subject: [PATCH 16/20] sdk/python: delegate __enter__ with @call_async --- sdk/python/src/dstack_sdk/dstack_client.py | 33 +++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 9c21220cb..c098a9e34 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -85,7 +85,12 @@ def _step_coro(coro): @functools.wraps(func) def wrapper(self, *args, **kwargs): - async_method = getattr(self.async_client, func.__name__) + magic_map = { + "__enter__": "__aenter__", + "__exit__": "__aexit__", + } + async_method_name = magic_map.get(func.__name__) or func.__name__ + async_method = getattr(self.async_client, async_method_name) return _step_coro(async_method(*args, **kwargs)) return wrapper @@ -417,25 +422,13 @@ def is_reachable(self) -> bool: """Return True if the service responds to a quick health call.""" raise NotImplementedError + @call_async def __enter__(self): - # Delegate to async client's context management for proper ref counting - self.async_client._client_ref_count += 1 - # Create sync client eagerly for sync context manager - if self.async_client.use_sync_http: - self.async_client._get_sync_client() - else: - self.async_client._get_client() - return self + raise NotImplementedError + @call_async def __exit__(self, exc_type, exc_val, exc_tb): - # Delegate to async client's context management for proper cleanup - self.async_client._client_ref_count -= 1 - if self.async_client._client_ref_count == 0: - # For sync context, we only clean up sync clients - # Async clients should not be created in sync context anyway - if self.async_client._sync_client: - self.async_client._sync_client.close() - self.async_client._sync_client = None + raise NotImplementedError class AsyncTappdClient(AsyncDstackClient): @@ -545,8 +538,10 @@ def tdx_quote( """Use ``get_quote`` instead (deprecated).""" raise NotImplementedError + @call_async def __enter__(self): - return self + raise NotImplementedError + @call_async def __exit__(self, exc_type, exc_val, exc_tb): - pass + raise NotImplementedError From 06fe9a9c15c8cc469d32bb7df1993213d97d69a0 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 18 Aug 2025 01:58:02 +0000 Subject: [PATCH 17/20] sdk/python: ruff format --- sdk/python/src/dstack_sdk/dstack_client.py | 2 - sdk/python/src/dstack_sdk/ethereum.py | 1 + sdk/python/src/dstack_sdk/solana.py | 1 + sdk/python/test_outputs.py | 92 ++++++++++++++-------- sdk/python/tests/test_client.py | 26 +++--- sdk/python/tests/test_typing.py | 28 ++++--- sdk/run-tests.sh | 3 +- 7 files changed, 92 insertions(+), 61 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index c098a9e34..6c43ca7df 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -197,7 +197,6 @@ class BaseClient: class AsyncDstackClient(BaseClient): - PATH_PREFIX = "/" def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): @@ -363,7 +362,6 @@ async def is_reachable(self) -> bool: class DstackClient(BaseClient): - PATH_PREFIX = "/" def __init__(self, endpoint: str | None = None): diff --git a/sdk/python/src/dstack_sdk/ethereum.py b/sdk/python/src/dstack_sdk/ethereum.py index b4b1cdc2a..71a97d58c 100644 --- a/sdk/python/src/dstack_sdk/ethereum.py +++ b/sdk/python/src/dstack_sdk/ethereum.py @@ -7,6 +7,7 @@ Use with ``dstack_sdk.DstackClient`` responses to create ``eth_account`` objects for signing and transacting. """ + import hashlib import warnings diff --git a/sdk/python/src/dstack_sdk/solana.py b/sdk/python/src/dstack_sdk/solana.py index a4e4ed4a4..05ce6d91f 100644 --- a/sdk/python/src/dstack_sdk/solana.py +++ b/sdk/python/src/dstack_sdk/solana.py @@ -7,6 +7,7 @@ Use with ``dstack_sdk.DstackClient`` responses to create ``solders.Keypair`` objects for signing transactions on Solana. """ + import hashlib import warnings diff --git a/sdk/python/test_outputs.py b/sdk/python/test_outputs.py index 5ae96a138..6dee15233 100644 --- a/sdk/python/test_outputs.py +++ b/sdk/python/test_outputs.py @@ -5,82 +5,103 @@ import asyncio import sys -from dstack_sdk import DstackClient, AsyncDstackClient, TappdClient, AsyncTappdClient, get_compose_hash, verify_env_encrypt_public_key +from dstack_sdk import ( + DstackClient, + AsyncDstackClient, + TappdClient, + AsyncTappdClient, + get_compose_hash, + verify_env_encrypt_public_key, +) async def main(): print("=== Python SDK Output Test ===") - + try: # Test client get_key client = DstackClient() print("\n1. Testing DstackClient.get_key()") - + test_paths = [ {"path": "test/wallet", "purpose": "ethereum"}, {"path": "test/signing", "purpose": "solana"}, - {"path": "user/alice", "purpose": "mainnet"} + {"path": "user/alice", "purpose": "mainnet"}, ] - + for test_case in test_paths: path, purpose = test_case["path"], test_case["purpose"] key_result = client.get_key(path, purpose) print(f"get_key('{path}', '{purpose}'):") print(f" key: {key_result.decode_key().hex()}") print(f" signature_chain length: {len(key_result.signature_chain)}") - print(f" signature_chain[0]: {key_result.decode_signature_chain()[0].hex()}") + print( + f" signature_chain[0]: {key_result.decode_signature_chain()[0].hex()}" + ) # Test viem integration (if available) print("\n2. Testing Viem Integration") eth_key = client.get_key("eth/test", "wallet") - + print("\n2.1 to_account (legacy):") try: from dstack_sdk.ethereum import to_account + account = to_account(eth_key) print(f" address: {account.address}") print(f" type: ethereum account") except ImportError: - print(" error: Ethereum integration not available (install with pip install 'dstack-sdk[eth]')") + print( + " error: Ethereum integration not available (install with pip install 'dstack-sdk[eth]')" + ) except Exception as error: print(f" error: {error}") - + print("\n2.2 to_account_secure:") try: from dstack_sdk.ethereum import to_account_secure + account_secure = to_account_secure(eth_key) print(f" address: {account_secure.address}") print(f" type: ethereum account") except ImportError: - print(" error: Ethereum integration not available (install with pip install 'dstack-sdk[eth]')") + print( + " error: Ethereum integration not available (install with pip install 'dstack-sdk[eth]')" + ) except Exception as error: print(f" error: {error}") # Test solana integration (if available) print("\n3. Testing Solana Integration") sol_key = client.get_key("sol/test", "wallet") - + print("\n3.1 to_keypair (legacy):") try: from dstack_sdk.solana import to_keypair + keypair = to_keypair(sol_key) print(f" publicKey: {keypair.pubkey()}") print(f" secretKey length: {len(bytes(keypair))}") print(f" secretKey (first 32 bytes): {bytes(keypair)[:32].hex()}") except ImportError: - print(" error: Solana integration not available (install with pip install 'dstack-sdk[sol]')") + print( + " error: Solana integration not available (install with pip install 'dstack-sdk[sol]')" + ) except Exception as error: print(f" error: {error}") - + print("\n3.2 to_keypair_secure:") try: from dstack_sdk.solana import to_keypair_secure + keypair_secure = to_keypair_secure(sol_key) print(f" publicKey: {keypair_secure.pubkey()}") print(f" secretKey length: {len(bytes(keypair_secure))}") print(f" secretKey (first 32 bytes): {bytes(keypair_secure)[:32].hex()}") except ImportError: - print(" error: Solana integration not available (install with pip install 'dstack-sdk[sol]')") + print( + " error: Solana integration not available (install with pip install 'dstack-sdk[sol]')" + ) except Exception as error: print(f" error: {error}") @@ -106,7 +127,9 @@ async def main(): try: async_tappd_client = AsyncTappdClient() print("\n4.3.1 AsyncTappdClient.get_key():") - async_tappd_key = await async_tappd_client.get_key("test/wallet", "ethereum") + async_tappd_key = await async_tappd_client.get_key( + "test/wallet", "ethereum" + ) print(f" key: {async_tappd_key.decode_key().hex()}") print(f" signature_chain length: {len(async_tappd_key.signature_chain)}") @@ -140,16 +163,16 @@ async def main(): "manifest_version": 1, "name": "test-app", "runner": "docker-compose", - "docker_compose_file": "services:\\n app:\\n image: test\\n ports:\\n - 8080:8080" + "docker_compose_file": "services:\\n app:\\n image: test\\n ports:\\n - 8080:8080", }, { "manifest_version": 1, - "name": "another-app", + "name": "another-app", "runner": "docker-compose", - "docker_compose_file": "services:\\n web:\\n build: .\\n environment:\\n - NODE_ENV=production" - } + "docker_compose_file": "services:\\n web:\\n build: .\\n environment:\\n - NODE_ENV=production", + }, ] - + for index, compose in enumerate(test_composes): hash_value = get_compose_hash(compose) print(f"compose {index + 1}: {hash_value}") @@ -160,23 +183,29 @@ async def main(): print("\n7. Testing verify_env_encrypt_public_key") test_cases = [ { - "public_key": bytes.fromhex('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a'), - "signature": bytes.fromhex('8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00'), - "app_id": '0000000000000000000000000000000000000000' + "public_key": bytes.fromhex( + "e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a" + ), + "signature": bytes.fromhex( + "8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00" + ), + "app_id": "0000000000000000000000000000000000000000", }, { - "public_key": bytes.fromhex('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'), - "signature": bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), - "app_id": 'invalid-app-id' - } + "public_key": bytes.fromhex( + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ), + "signature": bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ), + "app_id": "invalid-app-id", + }, ] for index, test_case in enumerate(test_cases): try: result = verify_env_encrypt_public_key( - test_case["public_key"], - test_case["signature"], - test_case["app_id"] + test_case["public_key"], test_case["signature"], test_case["app_id"] ) print(f"test case {index + 1}: {result.hex() if result else 'null'}") except Exception as error: @@ -186,5 +215,6 @@ async def main(): print(f"Error: {error}") sys.exit(1) + if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index dc8e71c4a..c1ad17580 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -263,14 +263,15 @@ def test_tappd_client_deprecated(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") TappdClient() - + # Filter for TappdClient deprecation warnings specifically tappd_warnings = [ - warning for warning in w - if issubclass(warning.category, DeprecationWarning) + warning + for warning in w + if issubclass(warning.category, DeprecationWarning) and "TappdClient is deprecated" in str(warning.message) ] - + assert len(tappd_warnings) == 1 assert "TappdClient is deprecated" in str(tappd_warnings[0].message) @@ -307,14 +308,15 @@ def test_async_tappd_client_deprecated(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") AsyncTappdClient() - + # Filter for AsyncTappdClient deprecation warnings specifically tappd_warnings = [ - warning for warning in w - if issubclass(warning.category, DeprecationWarning) + warning + for warning in w + if issubclass(warning.category, DeprecationWarning) and "AsyncTappdClient is deprecated" in str(warning.message) ] - + assert len(tappd_warnings) == 1 assert "AsyncTappdClient is deprecated" in str(tappd_warnings[0].message) @@ -329,9 +331,7 @@ async def test_async_tappd_client_derive_key_deprecated(): await client.derive_key("/", "test") # Should have warnings for both constructor and derive_key warning_messages = [str(warning.message) for warning in w] - assert any( - "AsyncTappdClient is deprecated" in msg for msg in warning_messages - ) + assert any("AsyncTappdClient is deprecated" in msg for msg in warning_messages) assert any("derive_key is deprecated" in msg for msg in warning_messages) @@ -345,9 +345,7 @@ async def test_async_tappd_client_tdx_quote_deprecated(): await client.tdx_quote("test data", "raw") # Should have warnings for both constructor and tdx_quote warning_messages = [str(warning.message) for warning in w] - assert any( - "AsyncTappdClient is deprecated" in msg for msg in warning_messages - ) + assert any("AsyncTappdClient is deprecated" in msg for msg in warning_messages) assert any("tdx_quote is deprecated" in msg for msg in warning_messages) diff --git a/sdk/python/tests/test_typing.py b/sdk/python/tests/test_typing.py index 5c635dda1..cd6d08183 100644 --- a/sdk/python/tests/test_typing.py +++ b/sdk/python/tests/test_typing.py @@ -41,9 +41,9 @@ def test_sync_method_type_annotations(): except Exception as e: print(f"Error getting type hints for get_tls_key: {e}") # If we can't get type hints, at least check it's not a coroutine function - assert not inspect.iscoroutinefunction( - get_tls_key_method - ), "Sync method should not be a coroutine function" + assert not inspect.iscoroutinefunction(get_tls_key_method), ( + "Sync method should not be a coroutine function" + ) def test_all_sync_method_types(): @@ -64,9 +64,9 @@ def test_all_sync_method_types(): assert callable(method), f"{method_name} should be callable" # Should not be coroutine function for sync client - assert not inspect.iscoroutinefunction( - method - ), f"Sync {method_name} should not be coroutine function" + assert not inspect.iscoroutinefunction(method), ( + f"Sync {method_name} should not be coroutine function" + ) try: type_hints = get_type_hints(method) @@ -80,7 +80,9 @@ def test_all_sync_method_types(): assert ( return_type == expected_return_type or return_type is expected_return_type - ), f"{method_name} should return {expected_return_type}, got {return_type}" + ), ( + f"{method_name} should return {expected_return_type}, got {return_type}" + ) except Exception as e: print(f"Warning: Could not get type hints for {method_name}: {e}") @@ -104,9 +106,9 @@ def test_async_method_types(): assert callable(method), f"{method_name} should be callable" # Should be coroutine function for async client - assert inspect.iscoroutinefunction( - method - ), f"Async {method_name} should be coroutine function" + assert inspect.iscoroutinefunction(method), ( + f"Async {method_name} should be coroutine function" + ) try: type_hints = get_type_hints(method) @@ -153,9 +155,9 @@ def test_method_signature_comparison(): if "self" in async_params: async_params.remove("self") - assert ( - sync_params == async_params - ), f"Parameter mismatch for {method_name}: sync={sync_params}, async={async_params}" + assert sync_params == async_params, ( + f"Parameter mismatch for {method_name}: sync={sync_params}, async={async_params}" + ) if __name__ == "__main__": diff --git a/sdk/run-tests.sh b/sdk/run-tests.sh index 8e1593c76..57b37f163 100755 --- a/sdk/run-tests.sh +++ b/sdk/run-tests.sh @@ -35,8 +35,9 @@ if [ ! -d .venv ]; then fi source .venv/bin/activate pip install -e . -pip install pytest pytest-asyncio evidence-api web3 solders +pip install pytest pytest-asyncio evidence-api web3 solders ruff pytest +ruff format --check --diff . popd pushd js/ From 268be672cdd8c73a30b9f0cfb1bf719662ff12fc Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Mon, 18 Aug 2025 15:20:59 +0800 Subject: [PATCH 18/20] build: use ruffy & pdm. --- sdk/python/pdm.lock | 241 ++++++++------------------------------ sdk/python/pyproject.toml | 104 +++++++--------- sdk/run-tests.sh | 17 +-- 3 files changed, 97 insertions(+), 265 deletions(-) diff --git a/sdk/python/pdm.lock b/sdk/python/pdm.lock index 58df8a8b7..5381bab06 100644 --- a/sdk/python/pdm.lock +++ b/sdk/python/pdm.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "ethereum", "lint", "solana", "test"] +groups = ["default", "dev", "ethereum", "lint", "solana"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:66159ee78fa4864888c47ee0dc6be4d633f01f108bdb1b6f73abfc1a38c870f1" +content_hash = "sha256:604d7126bdd0c3bcf7c2d5af6eb4b6ec77af13d54af2b1795f19a37ea0c276e1" [[metadata.targets]] requires_python = ">=3.10" @@ -194,7 +194,7 @@ name = "backports-asyncio-runner" version = "1.2.0" requires_python = "<3.11,>=3.8" summary = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." -groups = ["test"] +groups = ["dev"] marker = "python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, @@ -272,42 +272,6 @@ files = [ {file = "bitarray-3.6.1.tar.gz", hash = "sha256:4255bff37b01562b8e6adcf9db256029765985b0790c5ff76bbe1837edcd53ea"}, ] -[[package]] -name = "black" -version = "25.1.0" -requires_python = ">=3.9" -summary = "The uncompromising code formatter." -groups = ["lint"] -dependencies = [ - "click>=8.0.0", - "mypy-extensions>=0.4.3", - "packaging>=22.0", - "pathspec>=0.9.0", - "platformdirs>=2", - "tomli>=1.1.0; python_version < \"3.11\"", - "typing-extensions>=4.0.1; python_version < \"3.11\"", -] -files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, -] - [[package]] name = "certifi" version = "2025.8.3" @@ -502,27 +466,13 @@ files = [ {file = "ckzg-2.1.1.tar.gz", hash = "sha256:d6b306b7ec93a24e4346aa53d07f7f75053bc0afc7398e35fa649e5f9d48fcc4"}, ] -[[package]] -name = "click" -version = "8.2.1" -requires_python = ">=3.10" -summary = "Composable command line interface toolkit" -groups = ["lint"] -dependencies = [ - "colorama; platform_system == \"Windows\"", -] -files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, -] - [[package]] name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["lint", "test"] -marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +groups = ["dev"] +marker = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -803,7 +753,7 @@ name = "evidence-api" version = "0.5.0" requires_python = ">=3.6" summary = "Evidence API Base SDK" -groups = ["test"] +groups = ["dev"] files = [ {file = "evidence_api-0.5.0-py3-none-any.whl", hash = "sha256:4e60fd031f07afb557aa90777343136cf5b3dc0027f97ad322af1b2684b31d06"}, {file = "evidence_api-0.5.0.tar.gz", hash = "sha256:de2bcc2d74d409f3aba0e575b52ee60a55e32479d42ff848e422f7b9776e8376"}, @@ -814,7 +764,7 @@ name = "exceptiongroup" version = "1.3.0" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" -groups = ["default", "test"] +groups = ["default", "dev"] marker = "python_version < \"3.11\"" dependencies = [ "typing-extensions>=4.6.0; python_version < \"3.13\"", @@ -824,52 +774,6 @@ files = [ {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] -[[package]] -name = "flake8" -version = "7.3.0" -requires_python = ">=3.9" -summary = "the modular source code checker: pep8 pyflakes and co" -groups = ["lint"] -dependencies = [ - "mccabe<0.8.0,>=0.7.0", - "pycodestyle<2.15.0,>=2.14.0", - "pyflakes<3.5.0,>=3.4.0", -] -files = [ - {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, - {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, -] - -[[package]] -name = "flake8-docstrings" -version = "1.7.0" -requires_python = ">=3.7" -summary = "Extension for flake8 which uses pydocstyle to check docstrings" -groups = ["lint"] -dependencies = [ - "flake8>=3", - "pydocstyle>=2.1", -] -files = [ - {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, - {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, -] - -[[package]] -name = "flake8-pyproject" -version = "1.2.3" -requires_python = ">= 3.6" -summary = "Flake8 plug-in loading the configuration from pyproject.toml" -groups = ["lint"] -dependencies = [ - "Flake8>=5", - "TOMLi; python_version < \"3.11\"", - "TOMLi<2; python_version < \"3.7\"", -] -files = [ - {file = "flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a"}, -] - [[package]] name = "frozenlist" version = "1.7.0" @@ -1036,23 +940,12 @@ name = "iniconfig" version = "2.1.0" requires_python = ">=3.8" summary = "brain-dead simple config-ini parsing" -groups = ["test"] +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] -[[package]] -name = "isort" -version = "6.0.1" -requires_python = ">=3.9.0" -summary = "A Python utility / library to sort Python imports." -groups = ["lint"] -files = [ - {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, - {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, -] - [[package]] name = "jsonalias" version = "0.1.1" @@ -1064,17 +957,6 @@ files = [ {file = "jsonalias-0.1.1.tar.gz", hash = "sha256:64f04d935397d579fc94509e1fcb6212f2d081235d9d6395bd10baedf760a769"}, ] -[[package]] -name = "mccabe" -version = "0.7.0" -requires_python = ">=3.6" -summary = "McCabe checker, plugin for flake8" -groups = ["lint"] -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "multidict" version = "6.6.4" @@ -1184,7 +1066,7 @@ name = "mypy" version = "1.17.1" requires_python = ">=3.9" summary = "Optional static typing for Python" -groups = ["lint"] +groups = ["dev", "lint"] dependencies = [ "mypy-extensions>=1.0.0", "pathspec>=0.9.0", @@ -1231,7 +1113,7 @@ name = "mypy-extensions" version = "1.1.0" requires_python = ">=3.8" summary = "Type system extensions for programs checked with the mypy type checker." -groups = ["lint"] +groups = ["dev", "lint"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1242,7 +1124,7 @@ name = "packaging" version = "25.0" requires_python = ">=3.8" summary = "Core utilities for Python packages" -groups = ["lint", "test"] +groups = ["dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1266,29 +1148,18 @@ name = "pathspec" version = "0.12.1" requires_python = ">=3.8" summary = "Utility library for gitignore style pattern matching of file paths." -groups = ["lint"] +groups = ["dev", "lint"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "platformdirs" -version = "4.3.8" -requires_python = ">=3.9" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -groups = ["lint"] -files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, -] - [[package]] name = "pluggy" version = "1.6.0" requires_python = ">=3.9" summary = "plugin and hook calling mechanisms for python" -groups = ["test"] +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1385,17 +1256,6 @@ files = [ {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, ] -[[package]] -name = "pycodestyle" -version = "2.14.0" -requires_python = ">=3.9" -summary = "Python style guide checker" -groups = ["lint"] -files = [ - {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, - {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, -] - [[package]] name = "pycparser" version = "2.22" @@ -1551,38 +1411,12 @@ files = [ {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] -[[package]] -name = "pydocstyle" -version = "6.3.0" -requires_python = ">=3.6" -summary = "Python docstring style checker" -groups = ["lint"] -dependencies = [ - "importlib-metadata<5.0.0,>=2.0.0; python_version < \"3.8\"", - "snowballstemmer>=2.2.0", -] -files = [ - {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, - {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, -] - -[[package]] -name = "pyflakes" -version = "3.4.0" -requires_python = ">=3.9" -summary = "passive checker of Python programs" -groups = ["lint"] -files = [ - {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, - {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, -] - [[package]] name = "pygments" version = "2.19.2" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." -groups = ["test"] +groups = ["dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1593,7 +1427,7 @@ name = "pytest" version = "8.4.1" requires_python = ">=3.9" summary = "pytest: simple powerful testing with Python" -groups = ["test"] +groups = ["dev"] dependencies = [ "colorama>=0.4; sys_platform == \"win32\"", "exceptiongroup>=1; python_version < \"3.11\"", @@ -1613,7 +1447,7 @@ name = "pytest-asyncio" version = "1.1.0" requires_python = ">=3.9" summary = "Pytest support for asyncio" -groups = ["test"] +groups = ["dev"] dependencies = [ "backports-asyncio-runner<2,>=1.1; python_version < \"3.11\"", "pytest<9,>=8.2", @@ -1771,6 +1605,34 @@ files = [ {file = "rlp-4.1.0.tar.gz", hash = "sha256:be07564270a96f3e225e2c107db263de96b5bc1f27722d2855bd3459a08e95a9"}, ] +[[package]] +name = "ruff" +version = "0.12.9" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["dev"] +files = [ + {file = "ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e"}, + {file = "ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f"}, + {file = "ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7"}, + {file = "ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93"}, + {file = "ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908"}, + {file = "ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089"}, + {file = "ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a"}, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1782,17 +1644,6 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] -[[package]] -name = "snowballstemmer" -version = "3.0.1" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*" -summary = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." -groups = ["lint"] -files = [ - {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, - {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, -] - [[package]] name = "solders" version = "0.26.0" @@ -1820,7 +1671,7 @@ name = "tomli" version = "2.2.1" requires_python = ">=3.8" summary = "A lil' TOML parser" -groups = ["lint", "test"] +groups = ["dev", "lint"] marker = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, @@ -1888,7 +1739,7 @@ name = "typing-extensions" version = "4.14.1" requires_python = ">=3.9" summary = "Backported and Experimental Type Hints for Python 3.9+" -groups = ["default", "ethereum", "lint", "solana", "test"] +groups = ["default", "dev", "ethereum", "lint", "solana"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index bd93a5cc1..c0efd9d31 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -31,18 +31,8 @@ requires = ["pdm-backend"] build-backend = "pdm.backend" [dependency-groups] -test = [ - "pytest>=8.3.3", - "pytest-asyncio>=0.24.0", - "evidence-api>=0.5.0", -] lint = [ "mypy>=1.8.0", - "black>=24.0.0", - "isort>=5.12.0", - "flake8>=7.0.0", - "flake8-docstrings>=1.7.0", - "flake8-pyproject>=1.2.3", ] solana = [ "solders", @@ -67,59 +57,40 @@ warn_unreachable = true strict_equality = true show_error_codes = true -# Black configuration -[tool.black] +# Ruff configuration +[tool.ruff] line-length = 88 -target-version = ['py310'] -include = '\.pyi?$' -exclude = ''' -/( - \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist -)/ -''' - -# isort configuration -[tool.isort] -profile = "black" -line_length = 88 -# Enforce one import per line without parentheses -force_single_line = true -use_parentheses = false -include_trailing_comma = false -multi_line_output = 3 -force_grid_wrap = 0 -ensure_newline_before_comments = true - -# flake8 configuration -[tool.flake8] -max-line-length = 88 +target-version = "py310" + +[tool.ruff.lint] +extend-select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort + "D", # pydocstyle +] extend-ignore = [ "E203", # whitespace before ':' - "E501", # line too long (handled by black) - "W503", # line break before binary operator -] -exclude = [ - ".git", - "__pycache__", - "build", - "dist", - ".venv", - ".mypy_cache", - ".pytest_cache", -] -per-file-ignores = [ - "__init__.py:F401", # imported but unused - "tests/*.py:D100,D101,D102,D103,D104", # relax docstring rules for tests - "src/dstack_sdk/*.py:D100,D101,D102,D103,D104,D105,D106,D107", # relax all docstring rules for source code + "E501", # line too long (handled by formatter) + "D203", # one-blank-line-before-class (conflicts with D211) + "D213", # multi-line-summary-second-line (conflicts with D212) ] + +[tool.ruff.lint.isort] +force-single-line = true +force-sort-within-sections = true +combine-as-imports = false +known-first-party = ["dstack_sdk"] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] # imported but unused +"tests/*.py" = ["D100", "D101", "D102", "D103", "D104"] # relax docstring rules for tests +"src/dstack_sdk/*.py" = ["D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107"] # relax all docstring rules for source code + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + [tool.pdm] distribution = true @@ -128,12 +99,12 @@ repository = "pypi" [tool.pdm.scripts] # Formatting scripts -fmt = {composite = ["isort src/ tests/", "black src/ tests/"]} -format = {composite = ["isort src/ tests/", "black src/ tests/"]} +fmt = {composite = ["ruff format src/ tests/", "ruff check --fix src/ tests/"]} +format = {composite = ["ruff format src/ tests/", "ruff check --fix src/ tests/"]} # Linting scripts -lint = {composite = ["flake8 src/ tests/", "mypy src/"]} -check = {composite = ["black --check src/ tests/", "isort --check-only src/ tests/", "flake8 src/ tests/", "mypy src/"]} +lint = {composite = ["ruff check src/ tests/", "mypy src/"]} +check = {composite = ["ruff check src/ tests/", "ruff format --check src/ tests/", "mypy src/"]} # Testing scripts test = "pytest" @@ -150,6 +121,13 @@ markers = [ ] [tool.pdm.dev-dependencies] +dev = [ + "ruff>=0.12.9", + "mypy>=1.8.0", + "pytest>=8.3.3", + "pytest-asyncio>=0.24.0", + "evidence-api>=0.5.0", +] solana = [ "solders", ] diff --git a/sdk/run-tests.sh b/sdk/run-tests.sh index 57b37f163..557f991c8 100755 --- a/sdk/run-tests.sh +++ b/sdk/run-tests.sh @@ -30,14 +30,17 @@ DSTACK_SIMULATOR_ENDPOINT=$TAPPD_SIMULATOR_ENDPOINT go test -v ./tappd popd pushd python/ -if [ ! -d .venv ]; then - python -m venv .venv +# Ensure PDM is installed +if ! command -v pdm &> /dev/null; then + echo "Installing PDM..." + pip install pdm fi -source .venv/bin/activate -pip install -e . -pip install pytest pytest-asyncio evidence-api web3 solders ruff -pytest -ruff format --check --diff . +# Install dependencies and setup environment using PDM +pdm install --dev +# Run tests +pdm run test +# Run formatting check +pdm run check popd pushd js/ From 69be206a25311ca754c267f9d35b929e62409394 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Mon, 18 Aug 2025 17:03:14 +0800 Subject: [PATCH 19/20] chore: fmt --- sdk/python/src/dstack_sdk/dstack_client.py | 3 ++- sdk/python/src/dstack_sdk/encrypt_env_vars.py | 3 +-- sdk/python/src/dstack_sdk/ethereum.py | 4 ++-- sdk/python/src/dstack_sdk/get_compose_hash.py | 13 +++++-------- sdk/python/src/dstack_sdk/solana.py | 4 ++-- .../src/dstack_sdk/verify_env_encrypt_public_key.py | 3 +-- sdk/python/tests/test_client.py | 2 +- sdk/python/tests/test_ethereum.py | 2 +- 8 files changed, 15 insertions(+), 19 deletions(-) diff --git a/sdk/python/src/dstack_sdk/dstack_client.py b/sdk/python/src/dstack_sdk/dstack_client.py index 6c43ca7df..78f348440 100644 --- a/sdk/python/src/dstack_sdk/dstack_client.py +++ b/sdk/python/src/dstack_sdk/dstack_client.py @@ -9,12 +9,12 @@ import json import logging import os -import warnings from typing import Any from typing import Dict from typing import List from typing import Optional from typing import cast +import warnings import httpx from pydantic import BaseModel @@ -205,6 +205,7 @@ def __init__(self, endpoint: str | None = None, use_sync_http: bool = False): Args: endpoint: HTTP/HTTPS URL or Unix socket path use_sync_http: If True, use sync HTTP client internally + """ endpoint = get_endpoint(endpoint) self.use_sync_http = use_sync_http diff --git a/sdk/python/src/dstack_sdk/encrypt_env_vars.py b/sdk/python/src/dstack_sdk/encrypt_env_vars.py index 41f0da875..9b28cb4e8 100644 --- a/sdk/python/src/dstack_sdk/encrypt_env_vars.py +++ b/sdk/python/src/dstack_sdk/encrypt_env_vars.py @@ -2,8 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -""" -Environment variable encryption module for dstack SDK. +"""Environment variable encryption module for dstack SDK. Provides functionality to encrypt environment variables using X25519 key exchange and AES-GCM encryption, similar to the TypeScript implementation. diff --git a/sdk/python/src/dstack_sdk/ethereum.py b/sdk/python/src/dstack_sdk/ethereum.py index 71a97d58c..9475f8370 100644 --- a/sdk/python/src/dstack_sdk/ethereum.py +++ b/sdk/python/src/dstack_sdk/ethereum.py @@ -19,8 +19,7 @@ def to_account(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> LocalAccount: - """ - Create an Ethereum account from DstackClient key response. + """Create an Ethereum account from DstackClient key response. DEPRECATED: Use to_account_secure instead. This method has security concerns. Current implementation uses raw key material without proper hashing. @@ -30,6 +29,7 @@ def to_account(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> LocalAcc Returns: Account: Ethereum account object + """ if isinstance(get_key_response, GetTlsKeyResponse): warnings.warn( diff --git a/sdk/python/src/dstack_sdk/get_compose_hash.py b/sdk/python/src/dstack_sdk/get_compose_hash.py index b31cc0ade..3839c614f 100644 --- a/sdk/python/src/dstack_sdk/get_compose_hash.py +++ b/sdk/python/src/dstack_sdk/get_compose_hash.py @@ -2,8 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -""" -Compose hash calculation module for dstack SDK. +"""Compose hash calculation module for dstack SDK. Provides deterministic JSON serialization and SHA256 hashing of AppCompose configurations, compatible with the TypeScript implementation. @@ -153,8 +152,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "AppCompose": def sort_object(obj: Any) -> Any: - """ - Recursively sort object keys lexicographically. + """Recursively sort object keys lexicographically. This is crucial for deterministic JSON.stringify. """ @@ -185,8 +183,7 @@ def preprocess_app_compose(app_compose: AppCompose) -> AppCompose: def to_deterministic_json(app_compose: AppCompose) -> str: - """ - Serialize to deterministic JSON following cross-language standards. + """Serialize to deterministic JSON following cross-language standards. - Recursively sorts object keys lexicographically - Compact output (no spaces) @@ -224,8 +221,7 @@ def process_data(obj: Any) -> Any: def get_compose_hash( app_compose: Union[AppCompose, Dict[str, Any]], normalize: bool = False ) -> str: - """ - Calculate SHA256 hash of app compose configuration. + """Calculate SHA256 hash of app compose configuration. Args: app_compose: AppCompose object or dictionary @@ -233,6 +229,7 @@ def get_compose_hash( Returns: str: SHA256 hash as hex string + """ if isinstance(app_compose, dict): app_compose = AppCompose.from_dict(app_compose) diff --git a/sdk/python/src/dstack_sdk/solana.py b/sdk/python/src/dstack_sdk/solana.py index 05ce6d91f..ba8c1b153 100644 --- a/sdk/python/src/dstack_sdk/solana.py +++ b/sdk/python/src/dstack_sdk/solana.py @@ -18,8 +18,7 @@ def to_keypair(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> Keypair: - """ - Create a Solana Keypair from DstackClient key response. + """Create a Solana Keypair from DstackClient key response. DEPRECATED: Use to_keypair_secure instead. This method has security concerns. Current implementation uses raw key material without proper hashing. @@ -29,6 +28,7 @@ def to_keypair(get_key_response: GetKeyResponse | GetTlsKeyResponse) -> Keypair: Returns: Keypair: Solana keypair object + """ if isinstance(get_key_response, GetTlsKeyResponse): warnings.warn( diff --git a/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py b/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py index 2c432f294..902ec238b 100644 --- a/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py +++ b/sdk/python/src/dstack_sdk/verify_env_encrypt_public_key.py @@ -80,8 +80,7 @@ def verify_env_encrypt_public_key( def _recover_public_key( message_hash: bytes, r: int, s: int, recovery_id: int ) -> Optional[bytes]: - """ - Recover public key from ECDSA signature components. + """Recover public key from ECDSA signature components. This is a simplified implementation. In production, you should use a proper ECDSA recovery library like the one used in Ethereum. diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index c1ad17580..9f948df78 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -4,8 +4,8 @@ import warnings -import pytest from evidence_api.tdx.quote import TdxQuote +import pytest from dstack_sdk import AsyncDstackClient from dstack_sdk import AsyncTappdClient diff --git a/sdk/python/tests/test_ethereum.py b/sdk/python/tests/test_ethereum.py index 2bf748acb..c270ae5bc 100644 --- a/sdk/python/tests/test_ethereum.py +++ b/sdk/python/tests/test_ethereum.py @@ -4,8 +4,8 @@ import warnings -import pytest from eth_account.signers.local import LocalAccount +import pytest from dstack_sdk import GetKeyResponse from dstack_sdk.ethereum import to_account From 0f4a92a1f86451882941c2ecf2ef3c26276da676 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Mon, 18 Aug 2025 17:26:12 +0800 Subject: [PATCH 20/20] chore: bump python-sdk to 0.5.0 --- sdk/python/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index c0efd9d31..1e1c9df5e 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -4,10 +4,11 @@ [project] name = "dstack-sdk" -version = "0.2.0" +version = "0.5.0" description = "dstack SDK for Python" authors = [ {name = "Leechael Yim", email = "yanleech@gmail.com"}, + {name = "Kevin Wang", email = "wy721@qq.com"}, ] dependencies = [ "httpx>=0.27.2",