diff --git a/.gitignore b/.gitignore index dee9f09d7..173806972 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ !**/pdm.lock !**/README.md !**/.keep +!**/py.typed # Add Python code !**/*.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 6f8a69480..197e874b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -33,6 +33,38 @@ "DIPDUP_DEBUG": "1" } }, + { + "name": "demo_substrate_events: run", + "type": "debugpy", + "request": "launch", + "module": "dipdup", + "args": [ + "-e", + ".env", + "run" + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}/src/demo_substrate_events", + "env": { + "DIPDUP_DEBUG": "1" + } + }, + { + "name": "demo_substrate_events: init", + "type": "debugpy", + "request": "launch", + "module": "dipdup", + "args": [ + "-e", + ".env", + "init" + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}/src/demo_substrate_events", + "env": { + "DIPDUP_DEBUG": "1" + } + }, { "name": "demo_evm_events: run", "type": "debugpy", diff --git a/CHANGELOG.md b/CHANGELOG.md index f25f13bea..ff6b38ca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic Releases prior to 7.0 has been removed from this file to declutter search results; see the [archived copy](https://github.com/dipdup-io/dipdup/blob/8.0.0b5/CHANGELOG.md) for the full list. +## [Unreleased] + +### Fixed + +- config: Fixed setting default loglevels when `logging` is a dict. + ## [8.1.1] - 2024-10-17 ### Fixed diff --git a/Makefile b/Makefile index 6afa8c990..c7771dbd0 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,17 @@ FRONTEND_PATH=../interface help: ## Show this help (default) @grep -Fh "##" $(MAKEFILE_LIST) | grep -Fv grep -F | sed -e 's/\\$$//' | sed -e 's/##//' -install: +## +##-- Dependencies +## + +install: ## Install dependencies pdm sync --clean +update: ## Update dependencies and dump requirements.txt + pdm update + pdm export --without-hashes -f requirements --prod -o requirements.txt + ## ##-- CI ## @@ -53,13 +61,14 @@ mypy: ## Lint with mypy docs_build: docs docs: ## Build docs python scripts/docs.py check-links --source docs - python scripts/docs.py dump-references + # FIXME + # python scripts/docs.py dump-references || true python scripts/docs.py dump-demos python scripts/docs.py dump-metrics python scripts/docs.py dump-jsonschema python scripts/docs.py merge-changelog python scripts/docs.py markdownlint - python scripts/docs.py build --source docs --destination ${FRONTEND_PATH}/content/docs + # python scripts/docs.py build --source docs --destination ${FRONTEND_PATH}/content/docs docs_serve: ## Build docs and start frontend server python scripts/docs.py build --source docs --destination ${FRONTEND_PATH}/content/docs --watch --serve @@ -83,10 +92,6 @@ typeignore: ## Find type:ignore comments ##-- Release ## -update: ## Update dependencies and dump requirements.txt - pdm update - pdm export --without-hashes -f requirements --prod -o requirements.txt - demos: ## Recreate demo projects from templates python scripts/demos.py render ${DEMO} python scripts/demos.py init ${DEMO} diff --git a/docs/7.references/1.cli.md b/docs/7.references/1.cli.md index fb320104b..5d215b189 100644 --- a/docs/7.references/1.cli.md +++ b/docs/7.references/1.cli.md @@ -512,4 +512,4 @@ Discord: - \ No newline at end of file + diff --git a/docs/7.references/3.context.md b/docs/7.references/3.context.md index 84ba2c739..19c9fb20a 100644 --- a/docs/7.references/3.context.md +++ b/docs/7.references/3.context.md @@ -391,4 +391,4 @@ to provide a generic metadata interface (see docs).

None

- \ No newline at end of file + diff --git a/docs/7.references/4.models.md b/docs/7.references/4.models.md index 853172d53..a3bbb8c7d 100644 --- a/docs/7.references/4.models.md +++ b/docs/7.references/4.models.md @@ -859,4 +859,4 @@ description: "Models reference" - \ No newline at end of file + diff --git a/docs/8.examples/_demos_table.md b/docs/8.examples/_demos_table.md index 941c2cfb1..2615bfebf 100644 --- a/docs/8.examples/_demos_table.md +++ b/docs/8.examples/_demos_table.md @@ -2,6 +2,7 @@ | name | network | description | source | |-|-|-|-| | demo_blank | | Empty config for a fresh start | [link](https://github.com/dipdup-io/dipdup/tree/8.1.1/src/demo_blank) | +| demo_substrate_events | | Substrate balance transfers [PREVIEW] | [link](https://github.com/dipdup-io/dipdup/tree/8.1.1/src/demo_substrate_events) | | demo_evm_events | EVM | ERC-20 token transfers (from event logs) | [link](https://github.com/dipdup-io/dipdup/tree/8.1.1/src/demo_evm_events) | | demo_evm_transactions | EVM | ERC-20 token transfers (from transactions) | [link](https://github.com/dipdup-io/dipdup/tree/8.1.1/src/demo_evm_transactions) | | demo_evm_uniswap | EVM | Uniswap V3 pools, positions, etc. (advanced, uses TimescaleDB) | [link](https://github.com/dipdup-io/dipdup/tree/8.1.1/src/demo_evm_uniswap) | diff --git a/docs/9.release-notes/_8.0_changelog.md b/docs/9.release-notes/_8.0_changelog.md index f20cb17da..96d0390b3 100644 --- a/docs/9.release-notes/_8.0_changelog.md +++ b/docs/9.release-notes/_8.0_changelog.md @@ -32,6 +32,7 @@ - cli: Improved logging of indexer status. - config: Allow `sentry.dsn` to be empty string. - config: Fixed (de)serialization of hex strings in config. +- config: Fixed setting default loglevels when `logging` is a dict. - config: Fixed setting logging levels according to the config. - database: Fixed concurrency issue when using `get_or_create` method. - evm.events: Fixed matching logs when filtering by topic0. diff --git a/docs/config.rst b/docs/config.rst index 6c33c53b8..71342cc6b 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -7,14 +7,11 @@ .. autoclass:: dipdup.config.AdvancedConfig .. autoclass:: dipdup.config.ApiConfig .. autoclass:: dipdup.config.coinbase.CoinbaseDatasourceConfig -.. autoclass:: dipdup.config.ContractConfig -.. autoclass:: dipdup.config.DatasourceConfig .. autoclass:: dipdup.config.evm.EvmContractConfig .. autoclass:: dipdup.config.evm_node.EvmNodeDatasourceConfig .. autoclass:: dipdup.config.evm_events.EvmEventsHandlerConfig .. autoclass:: dipdup.config.evm_events.EvmEventsIndexConfig .. autoclass:: dipdup.config.evm_subsquid.EvmSubsquidDatasourceConfig -.. autoclass:: dipdup.config.evm.EvmIndexConfig .. autoclass:: dipdup.config.evm_transactions.EvmTransactionsHandlerConfig .. autoclass:: dipdup.config.evm_transactions.EvmTransactionsIndexConfig .. autoclass:: dipdup.config.HandlerConfig @@ -22,7 +19,6 @@ .. autoclass:: dipdup.config.HookConfig .. autoclass:: dipdup.config.HttpConfig .. autoclass:: dipdup.config.http.HttpDatasourceConfig -.. autoclass:: dipdup.config.IndexConfig .. autoclass:: dipdup.config.IndexDatasourceConfig .. autoclass:: dipdup.config.IndexTemplateConfig .. autoclass:: dipdup.config.ipfs.IpfsDatasourceConfig @@ -30,6 +26,7 @@ .. autoclass:: dipdup.config.PostgresDatabaseConfig .. autoclass:: dipdup.config.PrometheusConfig .. autoclass:: dipdup.config.ResolvedHttpConfig +.. autoclass:: dipdup.config.RuntimeConfig .. autoclass:: dipdup.config.SentryConfig .. autoclass:: dipdup.config.SqliteDatabaseConfig .. autoclass:: dipdup.config.SystemHookConfig @@ -57,9 +54,13 @@ .. autoclass:: dipdup.config.tezos_token_transfers.TezosTokenTransfersHandlerConfig .. autoclass:: dipdup.config.tezos_token_transfers.TezosTokenTransfersIndexConfig .. autoclass:: dipdup.config.starknet.StarknetContractConfig -.. autoclass:: dipdup.config.starknet.StarknetIndexConfig .. autoclass:: dipdup.config.starknet_events.StarknetEventsHandlerConfig .. autoclass:: dipdup.config.starknet_events.StarknetEventsIndexConfig .. autoclass:: dipdup.config.starknet_node.StarknetNodeDatasourceConfig .. autoclass:: dipdup.config.starknet_subsquid.StarknetSubsquidDatasourceConfig +.. autoclass:: dipdup.config.substrate.SubstrateRuntimeConfig +.. autoclass:: dipdup.config.substrate_events.SubstrateEventsHandlerConfig +.. autoclass:: dipdup.config.substrate_events.SubstrateEventsIndexConfig +.. autoclass:: dipdup.config.substrate_subsquid.SubstrateSubsquidDatasourceConfig +.. autoclass:: dipdup.config.substrate_subscan.SubstrateSubscanDatasourceConfig .. autoclass:: dipdup.config.tzip_metadata.TzipMetadataDatasourceConfig diff --git a/pdm.lock b/pdm.lock index 1564e3612..103361ef5 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "docs", "lint", "migrations", "perf", "test"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:d20bdcb8b0b8ce671b85925a71abbe6cd643993a333e7c00cbeb0546c10559e7" +content_hash = "sha256:0e1c65db1fbdd2e0588168a1a71c254498a2b55cac5dcefee53e6deb5a27f761" [[metadata.targets]] requires_python = ">=3.12,<3.13" @@ -30,13 +30,13 @@ files = [ [[package]] name = "aiohappyeyeballs" -version = "2.4.0" +version = "2.4.3" requires_python = ">=3.8" summary = "Happy Eyeballs for asyncio" groups = ["default", "test"] files = [ - {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, - {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, + {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, + {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, ] [[package]] @@ -186,13 +186,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.5.0" +version = "3.5.1" requires_python = ">=3.8" summary = "Bash tab completion for argparse" groups = ["default"] files = [ - {file = "argcomplete-3.5.0-py3-none-any.whl", hash = "sha256:d4bcf3ff544f51e16e54228a7ac7f486ed70ebf2ecfe49a63a91171c76bf029b"}, - {file = "argcomplete-3.5.0.tar.gz", hash = "sha256:4349400469dccfb7950bb60334a680c58d88699bff6159df61251878dc6bf74b"}, + {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, + {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, ] [[package]] @@ -272,28 +272,39 @@ files = [ {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] +[[package]] +name = "base58" +version = "2.1.1" +requires_python = ">=3.5" +summary = "Base58 and Base58Check implementation." +groups = ["default"] +files = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, +] + [[package]] name = "bitarray" -version = "2.9.2" +version = "3.0.0" summary = "efficient arrays of booleans -- C extension" groups = ["default"] files = [ - {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, - {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, - {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, - {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, - {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, - {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, - {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, - {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, - {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, - {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, - {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, - {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, - {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, - {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, - {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, - {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, + {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d"}, + {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9"}, + {file = "bitarray-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a"}, + {file = "bitarray-3.0.0-cp312-cp312-win32.whl", hash = "sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c"}, + {file = "bitarray-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999"}, + {file = "bitarray-3.0.0.tar.gz", hash = "sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03"}, ] [[package]] @@ -333,46 +344,46 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" requires_python = ">=3.7.0" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." groups = ["default", "docs", "test"] files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] name = "ckzg" -version = "2.0.0" +version = "2.0.1" summary = "Python bindings for C-KZG-4844" groups = ["default"] files = [ - {file = "ckzg-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5fb8a7ed9f430e1102f7d25df015e555c255c512c372373bd1b52fa65b2c32b2"}, - {file = "ckzg-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a93ef601f87960f881b6a2519d6689ee829cc35e0847ed3dff38c6afff383b41"}, - {file = "ckzg-2.0.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d0ca9e939b7b0dfd5a91cd981a595512000f42739b6262824c886b3a06960fe"}, - {file = "ckzg-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187a0fc230f3993fa8cb2c17d589f8b3ea6b74e1f5ac9927d4f37c19e153afd1"}, - {file = "ckzg-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a3c4aec3ffef2a20f67f6d4a13e9980560aa25d89bbc553aff1e4144f3239a"}, - {file = "ckzg-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb48fd7d110fda65a5b9f34f921d15d468354662752d252a0de02797e9510c50"}, - {file = "ckzg-2.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:de94dd1615e6aa003a6c864d5c8e8771d98ef912e32f12c555e7703134e77717"}, - {file = "ckzg-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:805d3a11bf6c50badaf02464340dcfb52363b1889b7f75b04a7179959285bac7"}, - {file = "ckzg-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea02a706d42e9c273554192949439742267b0031054d859c5c63db064b768a79"}, - {file = "ckzg-2.0.0.tar.gz", hash = "sha256:cd115a39cbc301b8465f6e19191cbb375b3589f3458cc995122595649a6f193f"}, + {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.tar.gz", hash = "sha256:62c5adc381637affa7e1df465c57750b356a761b8a3164c3106589b02532b9c9"}, ] [[package]] @@ -392,13 +403,13 @@ files = [ [[package]] name = "cloudpickle" -version = "3.0.0" +version = "3.1.0" requires_python = ">=3.8" summary = "Pickler class to extend the standard pickle.Pickler functionality" groups = ["perf"] files = [ - {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, - {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, + {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"}, + {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"}, ] [[package]] @@ -415,47 +426,47 @@ files = [ [[package]] name = "coverage" -version = "7.6.1" -requires_python = ">=3.8" +version = "7.6.3" +requires_python = ">=3.9" summary = "Code coverage measurement for Python" groups = ["test"] files = [ - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, + {file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"}, + {file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"}, + {file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"}, + {file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"}, + {file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"}, ] [[package]] name = "coverage" -version = "7.6.1" +version = "7.6.3" extras = ["toml"] -requires_python = ">=3.8" +requires_python = ">=3.9" summary = "Code coverage measurement for Python" groups = ["test"] dependencies = [ - "coverage==7.6.1", + "coverage==7.6.3", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, + {file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"}, + {file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"}, + {file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"}, + {file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"}, + {file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"}, ] [[package]] @@ -488,8 +499,8 @@ files = [ [[package]] name = "cytoolz" -version = "0.12.3" -requires_python = ">=3.7" +version = "1.0.0" +requires_python = ">=3.8" summary = "Cython implementation of Toolz: High performance functional utilities" groups = ["default"] marker = "implementation_name == \"cpython\"" @@ -497,26 +508,26 @@ dependencies = [ "toolz>=0.8.0", ] files = [ - {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, - {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, - {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, - {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, - {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, + {file = "cytoolz-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:10e3986066dc379e30e225b230754d9f5996aa8d84c2accc69c473c21d261e46"}, + {file = "cytoolz-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:16576f1bb143ee2cb9f719fcc4b845879fb121f9075c7c5e8a5ff4854bd02fc6"}, + {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3faa25a1840b984315e8b3ae517312375f4273ffc9a2f035f548b7f916884f37"}, + {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781fce70a277b20fd95dc66811d1a97bb07b611ceea9bda8b7dd3c6a4b05d59a"}, + {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a562c25338eb24d419d1e80a7ae12133844ce6fdeb4ab54459daf250088a1b2"}, + {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f29d8330aaf070304f7cd5cb7e73e198753624eb0aec278557cccd460c699b5b"}, + {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98a96c54aa55ed9c7cdb23c2f0df39a7b4ee518ac54888480b5bdb5ef69c7ef0"}, + {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:287d6d7f475882c2ddcbedf8da9a9b37d85b77690779a2d1cdceb5ae3998d52e"}, + {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:05a871688df749b982839239fcd3f8ec3b3b4853775d575ff9cd335fa7c75035"}, + {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:28bb88e1e2f7d6d4b8e0890b06d292c568984d717de3e8381f2ca1dd12af6470"}, + {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:576a4f1fc73d8836b10458b583f915849da6e4f7914f4ecb623ad95c2508cad5"}, + {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:509ed3799c47e4ada14f63e41e8f540ac6e2dab97d5d7298934e6abb9d3830ec"}, + {file = "cytoolz-1.0.0-cp312-cp312-win32.whl", hash = "sha256:9ce25f02b910630f6dc2540dd1e26c9326027ddde6c59f8cab07c56acc70714c"}, + {file = "cytoolz-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e53cfcce87e05b7f0ae2fb2b3e5820048cd0bb7b701e92bd8f75c9fbb7c9ae9"}, + {file = "cytoolz-1.0.0.tar.gz", hash = "sha256:eb453b30182152f9917a5189b7d99046b6ce90cdf8aeb0feff4b2683e600defd"}, ] [[package]] name = "datamodel-code-generator" -version = "0.26.1" +version = "0.26.2" requires_python = "<4.0,>=3.8" summary = "Datamodel Code Generator" groups = ["default"] @@ -536,8 +547,8 @@ dependencies = [ "toml<1.0.0,>=0.10.0; python_version < \"3.11\"", ] files = [ - {file = "datamodel_code_generator-0.26.1-py3-none-any.whl", hash = "sha256:bbe8a6cc0b9cfdbfd294e336e02b4c50b481ffc3b3c608b5578b6d7aa02cc8ae"}, - {file = "datamodel_code_generator-0.26.1.tar.gz", hash = "sha256:3b7b49c4230fa197ca28847e1e8996cd664638a7e91796c826a61c60d4ccd8a2"}, + {file = "datamodel_code_generator-0.26.2-py3-none-any.whl", hash = "sha256:f62576a27c9083f2b22cf8c97ed79a394155f131db3e3bf55cd72893f48c5d80"}, + {file = "datamodel_code_generator-0.26.2.tar.gz", hash = "sha256:03c153434d5a308e31fb4528c0199015054570642ccda8cd2f2cb3cc2c497622"}, ] [[package]] @@ -552,14 +563,14 @@ files = [ [[package]] name = "dnspython" -version = "2.6.1" -requires_python = ">=3.8" +version = "2.7.0" +requires_python = ">=3.9" summary = "DNS toolkit" groups = ["default"] marker = "python_version ~= \"3.11\"" files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, ] [[package]] @@ -637,7 +648,7 @@ files = [ [[package]] name = "eth-account" -version = "0.13.3" +version = "0.13.4" requires_python = "<4,>=3.8" summary = "eth-account: Sign Ethereum transactions and messages with local private keys" groups = ["default"] @@ -645,7 +656,7 @@ dependencies = [ "bitarray>=2.4.0", "ckzg>=2.0.0", "eth-abi>=4.0.0-b.2", - "eth-keyfile>=0.7.0", + "eth-keyfile<0.9.0,>=0.7.0", "eth-keys>=0.4.0", "eth-rlp>=2.1.0", "eth-utils>=2.0.0", @@ -654,8 +665,8 @@ dependencies = [ "rlp>=1.0.0", ] files = [ - {file = "eth_account-0.13.3-py3-none-any.whl", hash = "sha256:c8f3dae3403b8647f386fcc081fb8c2a0970991cf3e00af7e7ebd73f95d6a319"}, - {file = "eth_account-0.13.3.tar.gz", hash = "sha256:03d6af5d314e64b3dd53283e15b24736c5caa24542e5edac0455d6ff87d8b1e0"}, + {file = "eth_account-0.13.4-py3-none-any.whl", hash = "sha256:a4c109e9bad3a278243fcc028b755fb72b43e25b1e6256b3f309a44f5f7d87c3"}, + {file = "eth_account-0.13.4.tar.gz", hash = "sha256:2e1f2de240bef3d9f3d8013656135d2a79b6be6d4e7885bce9cace4334a4a376"}, ] [[package]] @@ -735,7 +746,7 @@ files = [ [[package]] name = "eth-typing" -version = "5.0.0" +version = "5.0.1" requires_python = "<4,>=3.8" summary = "eth-typing: Common type annotations for ethereum python packages" groups = ["default"] @@ -743,8 +754,8 @@ dependencies = [ "typing-extensions>=4.5.0", ] files = [ - {file = "eth_typing-5.0.0-py3-none-any.whl", hash = "sha256:c7ebc8595e7b65175bb4b4176c2b548ab21b13329f2058e84d4f8c289ba9f577"}, - {file = "eth_typing-5.0.0.tar.gz", hash = "sha256:87ce7cee75665c09d2dcff8de1b496609d5e32fcd2e2b1d8fc0370c29eedcdc0"}, + {file = "eth_typing-5.0.1-py3-none-any.whl", hash = "sha256:f30d1af16aac598f216748a952eeb64fbcb6e73efa691d2de31148138afe96de"}, + {file = "eth_typing-5.0.1.tar.gz", hash = "sha256:83debf88c9df286db43bb7374974681ebcc9f048fac81be2548dbc549a3203c0"}, ] [[package]] @@ -825,13 +836,13 @@ files = [ [[package]] name = "idna" -version = "3.8" +version = "3.10" requires_python = ">=3.6" summary = "Internationalized Domain Names in Applications (IDNA)" groups = ["default", "docs", "test"] files = [ - {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, - {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [[package]] @@ -953,22 +964,22 @@ files = [ [[package]] name = "markupsafe" -version = "2.1.5" -requires_python = ">=3.7" +version = "3.0.1" +requires_python = ">=3.9" summary = "Safely add untrusted strings to HTML/XML markup." groups = ["default", "docs", "perf"] files = [ - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, ] [[package]] @@ -987,19 +998,19 @@ files = [ [[package]] name = "marshmallow-dataclass" -version = "8.7.0" +version = "8.7.1" requires_python = ">=3.8" summary = "Python library to convert dataclasses into marshmallow schemas." groups = ["default"] dependencies = [ "marshmallow>=3.18.0", - "typeguard~=4.0.0", + "typeguard<5,>=4.0", "typing-extensions>=4.2.0; python_version < \"3.11\"", - "typing-inspect~=0.9.0", + "typing-inspect>=0.9.0", ] files = [ - {file = "marshmallow_dataclass-8.7.0-py3-none-any.whl", hash = "sha256:9e528d72b83f2b6b0f60cb29fd38781a6f7ce2155295adb1ed33289826a93c4b"}, - {file = "marshmallow_dataclass-8.7.0.tar.gz", hash = "sha256:0218008fec3fd4b5f739b2a0c6d7593bcc403308f6da953e341e4e359e268849"}, + {file = "marshmallow_dataclass-8.7.1-py3-none-any.whl", hash = "sha256:405cbaaad9cea56b3de2f85eff32a9880e3bf849f652e7f6de7395e4b1ddc072"}, + {file = "marshmallow_dataclass-8.7.1.tar.gz", hash = "sha256:4fb80e1bf7b31ce1b192aa87ffadee2cedb3f6f37bb0042f8500b07e6fad59c4"}, ] [[package]] @@ -1027,6 +1038,17 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "more-itertools" +version = "10.5.0" +requires_python = ">=3.8" +summary = "More routines for operating on iterables, beyond itertools" +groups = ["default"] +files = [ + {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, + {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -1039,49 +1061,52 @@ files = [ [[package]] name = "msgpack" -version = "1.0.8" +version = "1.1.0" requires_python = ">=3.8" summary = "MessagePack serializer" groups = ["default"] files = [ - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [[package]] name = "multidict" -version = "6.0.5" -requires_python = ">=3.7" +version = "6.1.0" +requires_python = ">=3.8" summary = "multidict implementation" groups = ["default", "test"] +dependencies = [ + "typing-extensions>=4.1.0; python_version < \"3.11\"", +] files = [ - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, + {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-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] [[package]] @@ -1118,20 +1143,22 @@ files = [ [[package]] name = "numpy" -version = "1.26.4" -requires_python = ">=3.9" +version = "2.1.2" +requires_python = ">=3.10" summary = "Fundamental package for array computing in Python" groups = ["perf"] files = [ - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, + {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, + {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, + {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, ] [[package]] @@ -1202,13 +1229,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" requires_python = ">=3.8" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." groups = ["default", "lint"] files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [[package]] @@ -1531,22 +1558,23 @@ files = [ [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" summary = "World timezone definitions, modern and historical" groups = ["default", "migrations"] files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] name = "pyunormalize" -version = "15.1.0" +version = "16.0.0" requires_python = ">=3.6" -summary = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." +summary = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent of the Python core Unicode database." groups = ["default"] files = [ - {file = "pyunormalize-15.1.0.tar.gz", hash = "sha256:cf4a87451a0f1cb76911aa97f432f4579e1f564a2f0c84ce488c73a73901b6c1"}, + {file = "pyunormalize-16.0.0-py3-none-any.whl", hash = "sha256:c647d95e5d1e2ea9a2f448d1d95d8518348df24eab5c3fd32d2b5c3300a49152"}, + {file = "pyunormalize-16.0.0.tar.gz", hash = "sha256:2e1dfbb4a118154ae26f70710426a52a364b926c9191f764601f5a8cb12761f7"}, ] [[package]] @@ -1582,27 +1610,27 @@ files = [ [[package]] name = "regex" -version = "2024.7.24" +version = "2024.9.11" requires_python = ">=3.8" summary = "Alternative regular expression module, to replace re." groups = ["default"] files = [ - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, - {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, - {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, - {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, + {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, + {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, + {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, ] [[package]] @@ -1624,18 +1652,18 @@ files = [ [[package]] name = "rich" -version = "13.8.0" -requires_python = ">=3.7.0" +version = "13.9.2" +requires_python = ">=3.8.0" summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" groups = ["perf"] dependencies = [ "markdown-it-py>=2.2.0", "pygments<3.0.0,>=2.13.0", - "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.11\"", ] files = [ - {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, - {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, + {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, + {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, ] [[package]] @@ -1687,29 +1715,45 @@ files = [ [[package]] name = "ruff" -version = "0.6.9" +version = "0.7.0" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["lint"] files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, + {file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"}, + {file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"}, + {file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"}, + {file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"}, + {file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"}, + {file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"}, + {file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"}, +] + +[[package]] +name = "scalecodec" +version = "1.2.11" +requires_python = "<4,>=3.6" +summary = "Python SCALE Codec Library" +groups = ["default"] +dependencies = [ + "base58>=2.0.1", + "more-itertools", + "requests>=2.24.0", +] +files = [ + {file = "scalecodec-1.2.11-py3-none-any.whl", hash = "sha256:d15c94965f617caa25096f83a45f5f73031d05e6ee08d6039969f0a64fc35de1"}, + {file = "scalecodec-1.2.11.tar.gz", hash = "sha256:99a2cdbfccdcaf22bd86b86da55a730a2855514ad2309faef4a4a93ac6cbeb8d"}, ] [[package]] @@ -1739,7 +1783,7 @@ files = [ [[package]] name = "sentry-sdk" -version = "2.16.0" +version = "2.17.0" requires_python = ">=3.6" summary = "Python client for Sentry (https://sentry.io)" groups = ["default"] @@ -1748,8 +1792,8 @@ dependencies = [ "urllib3>=1.26.11", ] files = [ - {file = "sentry_sdk-2.16.0-py2.py3-none-any.whl", hash = "sha256:49139c31ebcd398f4f6396b18910610a0c1602f6e67083240c33019d1f6aa30c"}, - {file = "sentry_sdk-2.16.0.tar.gz", hash = "sha256:90f733b32e15dfc1999e6b7aca67a38688a567329de4d6e184154a73f96c6892"}, + {file = "sentry_sdk-2.17.0-py2.py3-none-any.whl", hash = "sha256:625955884b862cc58748920f9e21efdfb8e0d4f98cca4ab0d3918576d5b606ad"}, + {file = "sentry_sdk-2.17.0.tar.gz", hash = "sha256:dd0a05352b78ffeacced73a94e86f38b32e2eae15fff5f30ca5abb568a72eacf"}, ] [[package]] @@ -2006,14 +2050,14 @@ files = [ [[package]] name = "toolz" -version = "0.12.1" -requires_python = ">=3.7" +version = "1.0.0" +requires_python = ">=3.8" summary = "List processing tools and functional utilities" groups = ["default"] marker = "implementation_name == \"pypy\" or implementation_name == \"cpython\"" files = [ - {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, - {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, + {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, + {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, ] [[package]] @@ -2036,17 +2080,17 @@ files = [ [[package]] name = "typeguard" -version = "4.0.1" -requires_python = ">=3.7.4" +version = "4.3.0" +requires_python = ">=3.8" summary = "Run-time type checker for Python" groups = ["default"] dependencies = [ "importlib-metadata>=3.6; python_version < \"3.10\"", - "typing-extensions>=4.7.0; python_version < \"3.12\"", + "typing-extensions>=4.10.0", ] files = [ - {file = "typeguard-4.0.1-py3-none-any.whl", hash = "sha256:43f55cc9953f26dae362adb973b6c9ad6b97bfffcc6757277912eddd5cfa345b"}, - {file = "typeguard-4.0.1.tar.gz", hash = "sha256:db35142d1f92fc8c1b954e5cc03b57810428f9cd4e4604647bdf5764fc5bbba9"}, + {file = "typeguard-4.3.0-py3-none-any.whl", hash = "sha256:4d24c5b39a117f8a895b9da7a9b3114f04eb63bade45a4492de49b175b6f7dfa"}, + {file = "typeguard-4.3.0.tar.gz", hash = "sha256:92ee6a0aec9135181eae6067ebd617fd9de8d75d714fb548728a4933b1dea651"}, ] [[package]] @@ -2062,7 +2106,7 @@ files = [ [[package]] name = "types-requests" -version = "2.32.0.20240712" +version = "2.32.0.20241016" requires_python = ">=3.8" summary = "Typing stubs for requests" groups = ["default"] @@ -2070,8 +2114,8 @@ dependencies = [ "urllib3>=2", ] files = [ - {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, - {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, ] [[package]] @@ -2113,14 +2157,14 @@ files = [ [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" requires_python = ">=2" summary = "Provider of IANA time zone data" groups = ["default"] marker = "platform_system == \"Windows\"" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -2140,13 +2184,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." groups = ["default", "docs", "test"] files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [[package]] @@ -2250,7 +2294,7 @@ files = [ [[package]] name = "yarl" -version = "1.15.3" +version = "1.15.4" requires_python = ">=3.9" summary = "Yet another URL library" groups = ["default", "test"] @@ -2260,22 +2304,22 @@ dependencies = [ "propcache>=0.2.0", ] files = [ - {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:decf9d76191bfe34835f1abd3fa8ebe8a9cd7e16300a5c7e82b18c0812bb22a2"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ce65ed7ad7b6cbca06b0c011b170bd2b0bc56b0a740540e2713e5ac12d7b9b2e"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cf2b50352df8775591869aaa22c52b64d60376ba99c0802b42778fedc90b775"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32e8ebf0080ddd38ec05f8be940a3719e5fe1ab8bb6d2b3f6f8b89c9e34149aa"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05183fd49244517cb11c208d0ae128f2e8a85ddb7caf22ad8b0ffcdf5481fcb6"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46653b5fd29e63ffe63335da343829a2b00bb43b0bd9bb21240d3b42629629e2"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6316af233610b9868eda92cf68c016750cbf50085ac6c51faa17905ddd25605"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5685ebc333c95b75be3a0a83a81b82b6411beee9585eaeb9e2e588ae8df23848"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6da6f6c6ee5595658f21bb9d1ecd702f7a7f22f224ac063dfb595624aec4a2e0"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45c05b87a8494d9820ea1ac82118fd2f1d795d868e94766fe8ff670377bf6280"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04f930fcc940f96b8b29110c56882bcff8703f87a7b9354d3acf60ffded5a23d"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8df77742b403e71c5d62d22d150e6e35efd6096a15f2c7419815911c62225100"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f785d83ece0998e4ce4fadda22fa6c1ecc40e10f41617013a8726d2e9af0d98f"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7794aade99be0d48b69bd5942acddfeff0de3d09c724d9abe4f19736708ef18f"}, - {file = "yarl-1.15.3-cp312-cp312-win32.whl", hash = "sha256:a3a98d70c667c957c7cd0b153d4cb5e45d43f5e2e23de73be6f7b5c883c01f72"}, - {file = "yarl-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:90257bc627897a2c1d562efcd6a6b18887e9dacae795cad2367e8e16df47d966"}, - {file = "yarl-1.15.3-py3-none-any.whl", hash = "sha256:a1d49ed6f4b812dde88e937d4c2bd3f13d72c23ef7de1e17a63b7cacef4b5691"}, - {file = "yarl-1.15.3.tar.gz", hash = "sha256:fbcff47f8ba82467f203037f7a30decf5c724211b224682f7236edb0dcbb5b95"}, + {file = "yarl-1.15.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f923e94e93a37fd990e8336e0b9bedea533e7cbed14e0c572bf9357ef2a70681"}, + {file = "yarl-1.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3198da7d7c34e29fc8c823e0c3ce6c7274aac35760de557c2017489c7d98fc5a"}, + {file = "yarl-1.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d886de2ea81f513ba2d6820451d33b767a97c37867ba688d42e164b2dbca1362"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ac85e760543129a1912a82438fc8075223e35eaa2d457d61cd83c27d00d17be"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e58c5d07b1f78dd4cb180c5b3b82465cd281aaeee8aafea0e5d72a4b97922cb1"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9060589d0acad1fca048861fa9ee3e8ed060f67894fa885969648ab6e9e99a54"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccd6774aa7bebdf9ca608bb0839318757a71b8e0d2cf7b10c002bc8790bd343e"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7694f109867ee428c21b85ae19fd31d164c691eb45cc95c561cfdeba237a12e3"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:83e7154aa0d17f5c93d27ac01088fd9ab6673e7bab1acbd07cd7a865b980c045"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f16d1940c0cbc342f1d29d6212a006d172be616d2942c5c41966e8a3ce4c3be1"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7d5226c70af3ad9569ccc4ccc04ab65be79eeb22c87d7ae789c89e62ef76bbd6"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f25906e4a72d9833e81717c39a39dee7297ff5cb44957d06d177a2ab8ef2ef7f"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e07e4b17b648c880e8e42bf1ac0a730bde114961646ae1c2ec4433f0c11ca94"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f8136bde8dfa4477c6a85c79a366581b4a505b51a52b669318fb631d3f4f638"}, + {file = "yarl-1.15.4-cp312-cp312-win32.whl", hash = "sha256:ccbeaf5b18b173b9d78e332e017b30ba8bedcf03cdce1d13490b82a3f421bc98"}, + {file = "yarl-1.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:f74f6ffdc633aefecbc80282242a5395058db9d1247fa7dd2f070ef84dc82583"}, + {file = "yarl-1.15.4-py3-none-any.whl", hash = "sha256:e5cc288111c450c0a54a74475591b206d3b1cb47dc71bb6200f6be8b1337184c"}, + {file = "yarl-1.15.4.tar.gz", hash = "sha256:a0c5e271058d148d730219ca4f33c5d841c6bd46e05b0da60fea7b516906ccd3"}, ] diff --git a/pyproject.toml b/pyproject.toml index eebbb1a0e..d6c85e438 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ dependencies = [ "tortoise-orm==0.21.7", "uvloop~=0.20", "web3~=7.2", + "scalecodec", ] [project.optional-dependencies] diff --git a/requirements.txt b/requirements.txt index fbedafd6b..1b030fcd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # This file is @generated by PDM. # Please do not edit it manually. -aiohappyeyeballs==2.4.0 +aiohappyeyeballs==2.4.3 aiohttp==3.10.10 aiolimiter==1.1.0 aiosignal==1.3.1 @@ -10,55 +10,57 @@ annotated-types==0.7.0 anyio==4.6.2.post1 appdirs==1.4.4 apscheduler==3.10.4 -argcomplete==3.5.0 +argcomplete==3.5.1 asgiref==3.8.1 async-lru==2.0.4 asyncpg==0.29.0 attrs==24.2.0 -bitarray==2.9.2 +base58==2.1.1 +bitarray==3.0.0 black==24.10.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -ckzg==2.0.0 +charset-normalizer==3.4.0 +ckzg==2.0.1 click==8.1.7 colorama==0.4.6; platform_system == "Windows" or sys_platform == "win32" crypto-cpp-py==1.4.4 -cytoolz==0.12.3; implementation_name == "cpython" -datamodel-code-generator==0.26.1 -dnspython==2.6.1; python_version ~= "3.11" +cytoolz==1.0.0; implementation_name == "cpython" +datamodel-code-generator==0.26.2 +dnspython==2.7.0; python_version ~= "3.11" ecdsa==0.18.0 email-validator==2.2.0; python_version ~= "3.11" eth-abi==5.1.0 -eth-account==0.13.3 +eth-account==0.13.4 eth-hash[pycryptodome]==0.7.0 eth-keyfile==0.8.1 eth-keys==0.5.1 eth-rlp==2.1.0 -eth-typing==5.0.0 +eth-typing==5.0.1 eth-utils==5.0.0 frozenlist==1.4.1 genson==1.3.0 hexbytes==1.2.1 -idna==3.8 +idna==3.10 inflect==5.6.2 iso8601==2.1.0 isort==5.13.2 jinja2==3.1.4 lark==1.2.2 lru-dict==1.3.0 -markupsafe==2.1.5 +markupsafe==3.0.1 marshmallow==3.22.0 -marshmallow-dataclass==8.7.0 +marshmallow-dataclass==8.7.1 marshmallow-oneofschema==3.1.1 +more-itertools==10.5.0 mpmath==1.3.0 -msgpack==1.0.8 -multidict==6.0.5 +msgpack==1.1.0 +multidict==6.1.0 mypy-extensions==1.0.0 orjson==3.10.7 packaging==24.1 parsimonious==0.10.0 pathspec==0.12.1 -platformdirs==4.2.2 +platformdirs==4.3.6 poseidon-py==0.1.5 prometheus-client==0.21.0 propcache==0.2.0 @@ -70,16 +72,17 @@ pypika-tortoise==0.2.1 pysignalr==1.0.0 python-dotenv==1.0.1 python-json-logger==2.0.7 -pytz==2024.1 -pyunormalize==15.1.0 +pytz==2024.2 +pyunormalize==16.0.0 pywin32==306; platform_system == "Windows" or sys_platform == "win32" or os_name == "nt" pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.9.11 requests==2.32.3 rlp==4.0.1 ruamel-yaml==0.18.6 ruamel-yaml-clib==0.2.8; platform_python_implementation == "CPython" and python_version < "3.13" -sentry-sdk==2.16.0 +scalecodec==1.2.11 +sentry-sdk==2.17.0 six==1.16.0 sniffio==1.3.1 sqlparse==0.5.1 @@ -88,16 +91,16 @@ strict-rfc3339==0.7 survey==5.4.0 sympy==1.11.1 tabulate==0.9.0 -toolz==0.12.1; implementation_name == "pypy" or implementation_name == "cpython" +toolz==1.0.0; implementation_name == "pypy" or implementation_name == "cpython" tortoise-orm==0.21.7 -typeguard==4.0.1 -types-requests==2.32.0.20240712 +typeguard==4.3.0 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 -tzdata==2024.1; platform_system == "Windows" +tzdata==2024.2; platform_system == "Windows" tzlocal==5.2 -urllib3==2.2.2 +urllib3==2.2.3 uvloop==0.21.0 web3==7.4.0 websockets==12.0 -yarl==1.15.3 +yarl==1.15.4 diff --git a/schemas/dipdup-3.0.json b/schemas/dipdup-3.0.json index 03b44de87..0ba206ead 100644 --- a/schemas/dipdup-3.0.json +++ b/schemas/dipdup-3.0.json @@ -11,7 +11,6 @@ "description": "always 'abi.etherscan'" }, "url": { - "default": "https://api.etherscan.io/api", "title": "url", "type": "string", "description": "API URL" @@ -44,7 +43,8 @@ } }, "required": [ - "kind" + "kind", + "url" ], "title": "AbiEtherscanDatasourceConfig", "type": "object" @@ -1549,6 +1549,247 @@ "title": "StarknetSubsquidDatasourceConfig", "type": "object" }, + "SubstrateEventsHandlerConfig": { + "additionalProperties": false, + "description": "Subsquid event handler", + "properties": { + "callback": { + "title": "callback", + "type": "string", + "description": "Callback name" + }, + "name": { + "title": "name", + "type": "string", + "description": "Event name (pallet.event)" + } + }, + "required": [ + "callback", + "name" + ], + "title": "SubstrateEventsHandlerConfig", + "type": "object" + }, + "SubstrateEventsIndexConfig": { + "additionalProperties": false, + "description": "Subsquid datasource config", + "properties": { + "kind": { + "const": "substrate.events", + "title": "kind", + "type": "string", + "description": "Always 'substrate.events'" + }, + "datasources": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/SubstrateSubsquidDatasourceConfig" + }, + { + "$ref": "#/$defs/SubstrateSubscanDatasourceConfig" + } + ] + }, + "title": "datasources", + "type": "array", + "description": "`substrate` datasources to use" + }, + "runtime": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/SubstrateRuntimeConfig" + } + ], + "title": "runtime", + "description": "Substrate runtime" + }, + "handlers": { + "items": { + "$ref": "#/$defs/SubstrateEventsHandlerConfig" + }, + "title": "handlers", + "type": "array", + "description": "Event handlers" + }, + "first_level": { + "default": 0, + "title": "first_level", + "type": "integer", + "description": "Level to start indexing from" + }, + "last_level": { + "default": 0, + "title": "last_level", + "type": "integer", + "description": "Level to stop indexing and disable this index" + } + }, + "required": [ + "kind", + "datasources", + "runtime", + "handlers" + ], + "title": "SubstrateEventsIndexConfig", + "type": "object" + }, + "SubstrateNodeDatasourceConfig": { + "additionalProperties": false, + "description": "Substrate node datasource config", + "properties": { + "kind": { + "const": "substrate.node", + "title": "kind", + "type": "string", + "description": "Always 'substrate.node'" + }, + "url": { + "$ref": "#/$defs/Url", + "title": "url", + "description": "Substrate node URL" + }, + "ws_url": { + "anyOf": [ + { + "$ref": "#/$defs/WsUrl" + }, + { + "type": "null" + } + ], + "default": null, + "title": "ws_url", + "description": "Substrate node WebSocket URL" + }, + "http": { + "anyOf": [ + { + "$ref": "#/$defs/HttpConfig" + }, + { + "type": "null" + } + ], + "default": null, + "title": "http", + "description": "HTTP client configuration" + } + }, + "required": [ + "kind", + "url" + ], + "title": "SubstrateNodeDatasourceConfig", + "type": "object" + }, + "SubstrateRuntimeConfig": { + "additionalProperties": false, + "description": "Substrate runtime config", + "properties": { + "kind": { + "const": "substrate", + "default": "substrate", + "title": "kind", + "type": "string", + "description": "Always 'substrate'" + } + }, + "title": "SubstrateRuntimeConfig", + "type": "object" + }, + "SubstrateSubscanDatasourceConfig": { + "additionalProperties": false, + "description": "Subscan datasource config", + "properties": { + "kind": { + "const": "substrate.subscan", + "title": "kind", + "type": "string", + "description": "always 'substrate.subscan'" + }, + "url": { + "title": "url", + "type": "string", + "description": "API URL" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "api_key", + "description": "API key" + }, + "http": { + "anyOf": [ + { + "$ref": "#/$defs/HttpConfig" + }, + { + "type": "null" + } + ], + "default": null, + "title": "http", + "description": "HTTP client configuration" + } + }, + "required": [ + "kind", + "url" + ], + "title": "SubstrateSubscanDatasourceConfig", + "type": "object" + }, + "SubstrateSubsquidDatasourceConfig": { + "additionalProperties": false, + "description": "Subsquid datasource config", + "properties": { + "kind": { + "const": "substrate.subsquid", + "title": "kind", + "type": "string", + "description": "always 'substrate.subsquid'" + }, + "url": { + "$ref": "#/$defs/Url", + "title": "url", + "description": "URL of Subsquid Network API" + }, + "http": { + "anyOf": [ + { + "$ref": "#/$defs/HttpConfig" + }, + { + "type": "null" + } + ], + "default": null, + "title": "http", + "description": "HTTP client configuration" + } + }, + "required": [ + "kind", + "url" + ], + "title": "SubstrateSubsquidDatasourceConfig", + "type": "object" + }, "TezosAddress": { "type": "string" }, @@ -2735,6 +2976,15 @@ }, { "$ref": "#/$defs/StarknetNodeDatasourceConfig" + }, + { + "$ref": "#/$defs/SubstrateSubsquidDatasourceConfig" + }, + { + "$ref": "#/$defs/SubstrateSubscanDatasourceConfig" + }, + { + "$ref": "#/$defs/SubstrateNodeDatasourceConfig" } ] }, @@ -2754,6 +3004,14 @@ "title": "database", "description": "Database config" }, + "runtimes": { + "additionalProperties": { + "$ref": "#/$defs/SubstrateRuntimeConfig" + }, + "title": "runtimes", + "type": "object", + "description": "Mapping of runtime aliases and runtime configs" + }, "contracts": { "additionalProperties": { "anyOf": [ @@ -2805,6 +3063,9 @@ { "$ref": "#/$defs/StarknetEventsIndexConfig" }, + { + "$ref": "#/$defs/SubstrateEventsIndexConfig" + }, { "$ref": "#/$defs/IndexTemplateConfig" } @@ -2846,6 +3107,9 @@ }, { "$ref": "#/$defs/StarknetEventsIndexConfig" + }, + { + "$ref": "#/$defs/SubstrateEventsIndexConfig" } ] }, diff --git a/scripts/docs.py b/scripts/docs.py index d8075965f..83f5de3b7 100755 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -117,12 +117,11 @@ class ReferencePage(TypedDict): # ## Title MD_HEADING_REGEX = r'\#\#* [\w ]*' +# NODE: Extract class name and subclasses in two groups; mind the classes without parents. # class AbiDatasourceConfig(DatasourceConfig): -CLASS_REGEX = r'class (\w*)[\(:]' +CLASS_REGEX = r'class ([a-zA-Z_0-9]*)[\(:]*(.*):' + -IGNORED_CONFIG_CLASSES = { - 'dipdup.config.Config', -} IGNORED_MODEL_CLASSES = { 'dipdup.models.BulkCreateQuery', 'dipdup.models.BulkUpdateQuery', @@ -515,7 +514,7 @@ def _compare(ref: str, ignore: set[str]) -> None: else: package_path_str = '.' + package_path.with_suffix('').as_posix().replace('/', '.') # NOTE: Skip private modules and classes - if '._' in package_path_str: + if '._' in package_path_str or 'ABC' in match.group(2): continue classes_in_package.add(f'dipdup.{ref}{package_path_str}.{match.group(1)}') @@ -528,7 +527,6 @@ def _compare(ref: str, ignore: set[str]) -> None: red_echo(f'=> Remove: {to_remove}') exit(1) - _compare('config', IGNORED_CONFIG_CLASSES) _compare('models', IGNORED_MODEL_CLASSES) green_echo('=> Building Sphinx docs') diff --git a/src/dipdup/codegen/__init__.py b/src/dipdup/codegen/__init__.py index 18bece4d6..e28c1ee16 100644 --- a/src/dipdup/codegen/__init__.py +++ b/src/dipdup/codegen/__init__.py @@ -26,6 +26,7 @@ from dipdup.project import render_base from dipdup.utils import load_template from dipdup.utils import pascal_to_snake +from dipdup.utils import sorted_glob from dipdup.utils import touch from dipdup.utils import write from dipdup.yaml import DipDupYAMLConfig @@ -136,7 +137,7 @@ async def generate_batch_handler(self) -> None: async def _generate_types(self, force: bool = False) -> None: """Generate typeclasses from fetched JSONSchemas: contract's storage, parameters, big maps and events.""" - for path in self._package.schemas.glob('**/*.json'): + for path in sorted_glob(self._package.schemas, '**/*.json'): await self._generate_type(path, force) async def _generate_type(self, schema_path: Path, force: bool) -> None: @@ -172,6 +173,7 @@ async def _generate_type(self, schema_path: Path, force: bool) -> None: custom_file_header=CODEGEN_HEADER, use_union_operator=True, output_model_type=dmcg.DataModelType.PydanticV2BaseModel, + use_schema_description=True, ) async def _generate_callback( diff --git a/src/dipdup/codegen/substrate.py b/src/dipdup/codegen/substrate.py new file mode 100644 index 000000000..551d32b04 --- /dev/null +++ b/src/dipdup/codegen/substrate.py @@ -0,0 +1,252 @@ +import logging +from collections import defaultdict +from pathlib import Path +from typing import Any +from typing import cast + +import orjson + +from dipdup.codegen import CodeGenerator +from dipdup.config import DipDupConfig +from dipdup.config import HandlerConfig +from dipdup.config.substrate import SubstrateIndexConfig +from dipdup.config.substrate_events import SubstrateEventsIndexConfig +from dipdup.config.substrate_subscan import SubstrateSubscanDatasourceConfig +from dipdup.datasources import Datasource +from dipdup.datasources.substrate_subscan import SubstrateSubscanDatasource +from dipdup.package import DipDupPackage +from dipdup.runtimes import SubstrateRuntime +from dipdup.runtimes import extract_args_name +from dipdup.utils import json_dumps +from dipdup.utils import pascal_to_snake +from dipdup.utils import snake_to_pascal +from dipdup.utils import sorted_glob +from dipdup.utils import write + +_logger = logging.getLogger(__name__) + + +def scale_type_to_jsonschema( + type_registry: dict[str, Any], + type_string: str, +) -> dict[str, Any]: + if type_string in type_registry['types']: + type_def = type_registry['types'][type_string] + if isinstance(type_def, str): + return scale_type_to_jsonschema(type_registry, type_def) + if isinstance(type_def, dict): + if 'type' in type_def: + return scale_type_to_jsonschema(type_registry, type_def['type']) + if '_enum' in type_def: + return { + 'description': type_string, + 'type': 'string', + 'enum': ( + list(type_def['_enum'].keys()) if isinstance(type_def['_enum'], dict) else type_def['_enum'] + ), + } + if '_struct' in type_def: + return { + 'description': type_string, + 'type': 'object', + 'properties': { + k: scale_type_to_jsonschema(type_registry, v) for k, v in type_def['_struct'].items() + }, + } + + # Handle primitives, default to str + schema: dict[str, Any] = { + 'description': type_string, + 'type': 'string', + } + + if type_string.lower() in ('u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128'): + schema['type'] = 'integer' + elif type_string == 'bool': + schema['type'] = 'boolean' + elif type_string in ['String', 'str']: + schema['type'] = 'string' + elif type_string.startswith('Vec<'): + inner_type = type_string[4:-1] + schema['type'] = 'array' + schema['items'] = scale_type_to_jsonschema(type_registry, inner_type) + elif type_string.startswith('Option<'): + inner_type = type_string[7:-1] + schema['oneOf'] = [{'type': 'null'}, scale_type_to_jsonschema(type_registry, inner_type)] + + return schema + + +def event_metadata_to_jsonschema( + type_registry: dict[str, Any], + metadata: dict[str, Any], +) -> dict[str, Any]: + description = '\n'.join(metadata['docs']) + args_name = [a for a in metadata.get('args_name', ()) if a] + if not args_name: + args_name = extract_args_name(description) + schema = { + '$schema': 'http://json-schema.org/draft-07/schema#', + 'title': metadata['name'], + 'description': description, + 'type': 'object', + 'properties': {}, + 'required': args_name, + } + for arg_name, arg_type in zip(args_name, metadata['args'], strict=True): + schema['properties'][arg_name] = scale_type_to_jsonschema(type_registry, arg_type) + schema['properties'][arg_name]['description'] = arg_type + + return schema + + +class SubstrateCodeGenerator(CodeGenerator): + def __init__( + self, + config: DipDupConfig, + package: DipDupPackage, + datasources: dict[str, Datasource[Any]], + include: set[str] | None = None, + ) -> None: + super().__init__(config, package, datasources, include) + + self._runtimes: dict[str, SubstrateRuntime] = {} + + async def generate_abis(self) -> None: + processed = set() + + for index_config in self._config.indexes.values(): + if not isinstance(index_config, SubstrateIndexConfig): + continue + name = index_config.runtime.name + if name in processed: + continue + + for datasource_config in index_config.datasources: + if isinstance(datasource_config, SubstrateSubscanDatasourceConfig): + datasource = cast(SubstrateSubscanDatasource, self._datasources[datasource_config.name]) + break + else: + raise NotImplementedError('Codegen currently requires `substrate.subscan` datasource') + + runtime_list = await datasource.get_runtime_list() + _logger.info('found %s runtimes', len(runtime_list)) + + for spec in runtime_list[::-1]: + spec_version = spec['spec_version'] + + key = f'v{spec_version}' + # NOTE: Important versions will be copied to project later + abi_path = self._package.abi_local.joinpath(f'{name}/{key}.json') + if abi_path.exists(): + continue + + _logger.info('v%s metadata not found, fetching', spec_version) + metadata = await datasource.get_runtime_metadata(spec_version) + write(abi_path, json_dumps(metadata)) + + processed.add(name) + + async def generate_schemas(self) -> None: + self._cleanup_schemas() + + handler_config: HandlerConfig + target_events: dict[str, list[str]] = {} + + for index_config in self._config.indexes.values(): + if isinstance(index_config, SubstrateEventsIndexConfig): + runtime_name = index_config.runtime.name + if runtime_name not in target_events: + target_events[runtime_name] = [] + for handler_config in index_config.handlers: + target_events[runtime_name].append(handler_config.name) + + if not target_events: + return + + latest_dumps = defaultdict(lambda: '') + + for runtime_name, events in target_events.items(): + for metadata_path in sorted_glob(self._package.abi_local, f'{runtime_name}/*.json'): + metadata = orjson.loads(metadata_path.read_bytes()) + + type_registry = self._get_runtime(runtime_name).runtime_config.type_registry + + for module in metadata: + for event_item in module.get('events', []): + qualname = f'{module["name"]}.{event_item["name"]}' + if qualname not in events: + continue + + # FIXME: ignore when only docs changed? + dump = orjson.dumps({**event_item, 'name': ''}) + if dump == latest_dumps[qualname]: + continue + latest_dumps[qualname] = dump + + # NOTE: Copy used abis to project + write(self._package.abi.joinpath(runtime_name, metadata_path.name), metadata_path.read_bytes()) + + schema_path = ( + self._package.schemas + / runtime_name + / 'substrate_events' + / pascal_to_snake(qualname) + / f'{metadata_path.stem.replace('.', '_')}.json' + ) + if schema_path.exists(): + continue + + jsonschema = event_metadata_to_jsonschema(type_registry, event_item) + + write(schema_path, json_dumps(jsonschema)) + + async def _generate_types(self, force: bool = False) -> None: + await super()._generate_types(force) + + for typeclass_dir in self._package.types.glob('**/substrate_events/*'): + + typeclass_name = f'{snake_to_pascal(typeclass_dir.name)}Payload' + + versions = [p.stem[1:] for p in typeclass_dir.glob('*.py')] + root_lines = [ + *(f'from .v{v} import V{v}' for v in versions), + '', + f'type {typeclass_name} = ' + ' | '.join(f'V{v}' for v in versions), + '', + ] + + write(typeclass_dir.joinpath('__init__.py'), '\n'.join(root_lines), overwrite=True) + + async def generate_hooks(self) -> None: + pass + + async def generate_system_hooks(self) -> None: + pass + + async def generate_handlers(self) -> None: + pass + + def get_typeclass_name(self, schema_path: Path) -> str: + module_name = schema_path.stem + if schema_path.parent.name == 'substrate_events': + class_name = f'{module_name}_payload' + else: + class_name = module_name + return snake_to_pascal(class_name) + + async def _generate_type(self, schema_path: Path, force: bool) -> None: + markers = { + 'substrate_events', + } + if not set(schema_path.parts).intersection(markers): + return + await super()._generate_type(schema_path, force) + + def _get_runtime(self, name: str) -> SubstrateRuntime: + if name not in self._runtimes: + self._runtimes[name] = SubstrateRuntime( + name=name, + package=self._package, + ) + return self._runtimes[name] diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 627a91ee4..b8f0e507c 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -249,7 +249,7 @@ class ContractConfig(ABC, NameMixin): """Contract config :param kind: Defined by child class - :param typename: Alias for the contract script + :param typename: Alias for the typeclass directory """ kind: str @@ -264,6 +264,17 @@ def module_path(self) -> Path: return Path(*self.module_name.split('.')) +# FIXME: we use Substrate runtimes as contracts for codegen +class RuntimeConfig(ContractConfig): + """Runtime config + + :param kind: Defined by child class + :param typename: Alias for the typeclass directory + """ + + pass + + class DatasourceConfig(ABC, NameMixin): """Base class for datasource configs @@ -565,6 +576,7 @@ class DipDupConfig: :param package: Name of indexer's Python package, existing or not :param datasources: Mapping of datasource aliases and datasource configs :param database: Database config + :param runtimes: Mapping of runtime aliases and runtime configs :param contracts: Mapping of contract aliases and contract configs :param indexes: Mapping of index aliases and index configs :param templates: Mapping of template aliases and index templates @@ -585,6 +597,7 @@ class DipDupConfig: database: SqliteDatabaseConfig | PostgresDatabaseConfig = Field( default_factory=lambda *a, **kw: SqliteDatabaseConfig(kind='sqlite') ) + runtimes: dict[str, RuntimeConfigU] = Field(default_factory=dict) contracts: dict[str, ContractConfigU] = Field(default_factory=dict) indexes: dict[str, IndexConfigU] = Field(default_factory=dict) templates: dict[str, ResolvedIndexConfigU] = Field(default_factory=dict) @@ -796,13 +809,24 @@ def get_abi_etherscan_datasource(self, name: str) -> AbiEtherscanDatasourceConfi raise ConfigurationError('`datasource` field must refer to Etherscan datasource') return datasource + def get_substrate_subsquid_datasource(self, name: str) -> SubstrateSubsquidDatasourceConfig: + datasource = self.get_datasource(name) + if not isinstance(datasource, SubstrateSubsquidDatasourceConfig): + raise ConfigurationError('`datasource` field must refer to Subsquid datasource') + return datasource + def set_up_logging(self) -> None: - loglevels = {} if isinstance(self.logging, dict): - loglevels = {**self.logging} + loglevels = { + 'dipdup': 'INFO', + self.package: 'INFO', + **self.logging, + } else: - loglevels['dipdup'] = self.logging - loglevels[self.package] = self.logging + loglevels = { + 'dipdup': self.logging, + self.package: self.logging, + } # NOTE: Environment variables have higher priority if env.DEBUG: @@ -823,7 +847,7 @@ def set_up_logging(self) -> None: def initialize(self) -> None: self._set_names() self._resolve_templates() - self._resolve_links() + self._resolve_aliases() self._validate() def dump(self) -> str: @@ -949,7 +973,7 @@ def _resolve_templates(self) -> None: if isinstance(index_config, IndexTemplateConfig): self._resolve_template(index_config) - def _resolve_links(self) -> None: + def _resolve_aliases(self) -> None: for index_config in self.indexes.values(): if isinstance(index_config, IndexTemplateConfig): raise ConfigInitializationException('Index templates must be resolved first') @@ -1072,6 +1096,13 @@ def _resolve_index_links(self, index_config: ResolvedIndexConfigU) -> None: if isinstance(handler_config.contract, str): handler_config.contract = self.get_starknet_contract(handler_config.contract) + + elif isinstance(index_config, SubstrateEventsIndexConfig): + if isinstance(index_config.runtime, str): + index_config.runtime = self.runtimes[index_config.runtime] + for handler_config in index_config.handlers: + handler_config.parent = index_config + else: raise NotImplementedError(f'Index kind `{index_config.kind}` is not supported') @@ -1082,6 +1113,7 @@ def _set_names(self) -> None: ( self.contracts, self.datasources, + self.runtimes, self.hooks, self.jobs, self.templates, @@ -1112,6 +1144,11 @@ def _set_names(self) -> None: from dipdup.config.starknet_events import StarknetEventsIndexConfig from dipdup.config.starknet_node import StarknetNodeDatasourceConfig from dipdup.config.starknet_subsquid import StarknetSubsquidDatasourceConfig +from dipdup.config.substrate import SubstrateRuntimeConfig +from dipdup.config.substrate_events import SubstrateEventsIndexConfig +from dipdup.config.substrate_node import SubstrateNodeDatasourceConfig +from dipdup.config.substrate_subscan import SubstrateSubscanDatasourceConfig +from dipdup.config.substrate_subsquid import SubstrateSubsquidDatasourceConfig from dipdup.config.tezos import TezosContractConfig from dipdup.config.tezos_big_maps import TezosBigMapsIndexConfig from dipdup.config.tezos_events import TezosEventsIndexConfig @@ -1128,6 +1165,7 @@ def _set_names(self) -> None: from dipdup.config.tzip_metadata import TzipMetadataDatasourceConfig # NOTE: Unions for Pydantic config deserialization +RuntimeConfigU = SubstrateRuntimeConfig ContractConfigU = EvmContractConfig | TezosContractConfig | StarknetContractConfig DatasourceConfigU = ( CoinbaseDatasourceConfig @@ -1140,6 +1178,9 @@ def _set_names(self) -> None: | TezosTzktDatasourceConfig | StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig + | SubstrateSubsquidDatasourceConfig + | SubstrateSubscanDatasourceConfig + | SubstrateNodeDatasourceConfig ) TezosIndexConfigU = ( TezosBigMapsIndexConfig @@ -1152,8 +1193,9 @@ def _set_names(self) -> None: ) EvmIndexConfigU = EvmEventsIndexConfig | EvmTransactionsIndexConfig StarknetIndexConfigU = StarknetEventsIndexConfig +SubstrateIndexConfigU = SubstrateEventsIndexConfig -ResolvedIndexConfigU = TezosIndexConfigU | EvmIndexConfigU | StarknetIndexConfigU +ResolvedIndexConfigU = TezosIndexConfigU | EvmIndexConfigU | StarknetIndexConfigU | SubstrateIndexConfigU IndexConfigU = ResolvedIndexConfigU | IndexTemplateConfig diff --git a/src/dipdup/config/abi_etherscan.py b/src/dipdup/config/abi_etherscan.py index 79520a1aa..e69f40795 100644 --- a/src/dipdup/config/abi_etherscan.py +++ b/src/dipdup/config/abi_etherscan.py @@ -8,8 +8,6 @@ from dipdup.config import AbiDatasourceConfig from dipdup.config import HttpConfig -DEFAULT_ETHERSCAN_URL = 'https://api.etherscan.io/api' - @dataclass(config=ConfigDict(extra='forbid'), kw_only=True) class AbiEtherscanDatasourceConfig(AbiDatasourceConfig): @@ -22,7 +20,7 @@ class AbiEtherscanDatasourceConfig(AbiDatasourceConfig): """ kind: Literal['abi.etherscan'] - url: str = DEFAULT_ETHERSCAN_URL + url: str api_key: str | None = None http: HttpConfig | None = None diff --git a/src/dipdup/config/substrate.py b/src/dipdup/config/substrate.py new file mode 100644 index 000000000..5286636e9 --- /dev/null +++ b/src/dipdup/config/substrate.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from abc import ABC +from typing import Literal +from typing import TypeAlias + +from pydantic import ConfigDict +from pydantic.dataclasses import dataclass + +from dipdup.config import Alias +from dipdup.config import IndexConfig +from dipdup.config import RuntimeConfig +from dipdup.config.substrate_subscan import SubstrateSubscanDatasourceConfig +from dipdup.config.substrate_subsquid import SubstrateSubsquidDatasourceConfig + +SubstrateDatasourceConfigU: TypeAlias = SubstrateSubsquidDatasourceConfig | SubstrateSubscanDatasourceConfig + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateRuntimeConfig(RuntimeConfig): + """Substrate runtime config + + :param kind: Always 'substrate' + """ + + kind: Literal['substrate'] = 'substrate' + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateIndexConfig(IndexConfig, ABC): + """EVM index that use Subsquid Network as a datasource + + :param kind: starts with 'substrate' + :param datasources: `substrate` datasources to use + :param runtime: Substrate runtime + """ + + datasources: tuple[Alias[SubstrateDatasourceConfigU], ...] + runtime: Alias[SubstrateRuntimeConfig] diff --git a/src/dipdup/config/substrate_events.py b/src/dipdup/config/substrate_events.py new file mode 100644 index 000000000..d4391437a --- /dev/null +++ b/src/dipdup/config/substrate_events.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Literal +from typing import cast + +from pydantic import ConfigDict +from pydantic.dataclasses import dataclass + +from dipdup.config import Alias +from dipdup.config import HandlerConfig +from dipdup.config.substrate import SubstrateDatasourceConfigU +from dipdup.config.substrate import SubstrateIndexConfig +from dipdup.config.substrate import SubstrateRuntimeConfig +from dipdup.utils import pascal_to_snake +from dipdup.utils import snake_to_pascal + +if TYPE_CHECKING: + from collections.abc import Iterator + +from dipdup.subscriptions import Subscription + + +@dataclass(frozen=True) +class DummySubscription(Subscription): + pass + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateEventsHandlerConfig(HandlerConfig): + """Subsquid event handler + + :param callback: Callback name + :param name: Event name (pallet.event) + """ + + name: str + + def iter_imports(self, package: str) -> Iterator[tuple[str, str]]: + yield 'dipdup.context', 'HandlerContext' + yield 'dipdup.models.substrate', 'SubstrateEvent' + yield package, 'models as models' + + event_cls = snake_to_pascal(self.name) + 'Payload' + event_module = pascal_to_snake(self.name) + + parent = cast(SubstrateIndexConfig, self.parent) + yield f'{package}.types.{parent.runtime.name}.substrate_events.{event_module}', event_cls + + def iter_arguments(self) -> Iterator[tuple[str, str]]: + event_cls = snake_to_pascal(self.name) + 'Payload' + yield 'ctx', 'HandlerContext' + yield 'event', f'SubstrateEvent[{event_cls}]' + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateEventsIndexConfig(SubstrateIndexConfig): + """Subsquid datasource config + + :param kind: Always 'substrate.events' + :param datasources: `substrate` datasources to use + :param handlers: Event handlers + :param first_level: Level to start indexing from + :param last_level: Level to stop indexing and disable this index + :param typename: Alias for pallet interface + :param runtime: Substrate runtime + """ + + kind: Literal['substrate.events'] + datasources: tuple[Alias[SubstrateDatasourceConfigU], ...] + handlers: tuple[SubstrateEventsHandlerConfig, ...] + runtime: Alias[SubstrateRuntimeConfig] + + first_level: int = 0 + last_level: int = 0 + + def get_subscriptions(self) -> set[Subscription]: + # FIXME: or get_sync_level fails + return {DummySubscription()} diff --git a/src/dipdup/config/substrate_node.py b/src/dipdup/config/substrate_node.py new file mode 100644 index 000000000..d1501623a --- /dev/null +++ b/src/dipdup/config/substrate_node.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import ConfigDict +from pydantic.dataclasses import dataclass + +from dipdup.config import HttpConfig +from dipdup.config import IndexDatasourceConfig +from dipdup.config import Url +from dipdup.config import WsUrl + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateNodeDatasourceConfig(IndexDatasourceConfig): + """Substrate node datasource config + + :param kind: Always 'substrate.node' + :param url: Substrate node URL + :param ws_url: Substrate node WebSocket URL + :param http: HTTP client configuration + """ + + kind: Literal['substrate.node'] + url: Url + ws_url: WsUrl | None = None + http: HttpConfig | None = None + + @property + def merge_subscriptions(self) -> bool: + return False + + @property + def rollback_depth(self) -> int: + # FIXME: + return 0 diff --git a/src/dipdup/config/substrate_subscan.py b/src/dipdup/config/substrate_subscan.py new file mode 100644 index 000000000..1218b003a --- /dev/null +++ b/src/dipdup/config/substrate_subscan.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import ConfigDict +from pydantic.dataclasses import dataclass + +from dipdup.config import AbiDatasourceConfig +from dipdup.config import HttpConfig + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateSubscanDatasourceConfig(AbiDatasourceConfig): + """Subscan datasource config + + :param kind: always 'substrate.subscan' + :param url: API URL + :param api_key: API key + :param http: HTTP client configuration + """ + + kind: Literal['substrate.subscan'] + url: str + api_key: str | None = None + + http: HttpConfig | None = None diff --git a/src/dipdup/config/substrate_subsquid.py b/src/dipdup/config/substrate_subsquid.py new file mode 100644 index 000000000..cdc660d42 --- /dev/null +++ b/src/dipdup/config/substrate_subsquid.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import ConfigDict +from pydantic.dataclasses import dataclass + +from dipdup.config import HttpConfig +from dipdup.config import IndexDatasourceConfig +from dipdup.config import Url + + +@dataclass(config=ConfigDict(extra='forbid'), kw_only=True) +class SubstrateSubsquidDatasourceConfig(IndexDatasourceConfig): + """Subsquid datasource config + + :param kind: always 'substrate.subsquid' + :param url: URL of Subsquid Network API + :param http: HTTP client configuration + """ + + kind: Literal['substrate.subsquid'] + url: Url + http: HttpConfig | None = None + + @property + def merge_subscriptions(self) -> bool: + return False + + @property + def rollback_depth(self) -> int: + return 0 diff --git a/src/dipdup/context.py b/src/dipdup/context.py index e5d27812f..6c28357db 100644 --- a/src/dipdup/context.py +++ b/src/dipdup/context.py @@ -29,6 +29,8 @@ from dipdup.config.evm_transactions import EvmTransactionsIndexConfig from dipdup.config.starknet import StarknetIndexConfig from dipdup.config.starknet_events import StarknetEventsIndexConfig +from dipdup.config.substrate import SubstrateIndexConfig +from dipdup.config.substrate_events import SubstrateEventsIndexConfig from dipdup.config.tezos import TezosContractConfig from dipdup.config.tezos import TezosIndexConfig from dipdup.config.tezos_big_maps import TezosBigMapsIndexConfig @@ -52,6 +54,9 @@ from dipdup.datasources.ipfs import IpfsDatasource from dipdup.datasources.starknet_node import StarknetNodeDatasource from dipdup.datasources.starknet_subsquid import StarknetSubsquidDatasource +from dipdup.datasources.substrate_node import SubstrateNodeDatasource +from dipdup.datasources.substrate_subscan import SubstrateSubscanDatasource +from dipdup.datasources.substrate_subsquid import SubstrateSubsquidDatasource from dipdup.datasources.tezos_tzkt import TezosTzktDatasource from dipdup.datasources.tzip_metadata import TzipMetadataDatasource from dipdup.exceptions import CallbackError @@ -67,6 +72,8 @@ from dipdup.indexes.evm_transactions.index import EvmTransactionsIndex from dipdup.indexes.starknet import StarknetIndex from dipdup.indexes.starknet_events.index import StarknetEventsIndex +from dipdup.indexes.substrate import SubstrateIndex +from dipdup.indexes.substrate_events.index import SubstrateEventsIndex from dipdup.indexes.tezos_big_maps.index import TezosBigMapsIndex from dipdup.indexes.tezos_events.index import TezosEventsIndex from dipdup.indexes.tezos_head.index import TezosHeadIndex @@ -332,6 +339,8 @@ async def _spawn_index( index = self._create_tezos_index(index_config) elif isinstance(index_config, StarknetIndexConfig): index = self._create_starknet_index(index_config) + elif isinstance(index_config, SubstrateIndexConfig): + index = self._create_substrate_index(index_config) else: raise NotImplementedError @@ -384,6 +393,22 @@ def _create_starknet_index(self, index_config: StarknetIndexConfig) -> StarknetI return index + def _create_substrate_index(self, index_config: SubstrateIndexConfig) -> SubstrateIndex[Any, Any, Any]: + datasource_configs = index_config.datasources + datasources = tuple(self.get_substrate_datasource(c.name) for c in datasource_configs) + index_datasources = tuple(d for d in datasources if isinstance(d, IndexDatasource)) + + for datasource in index_datasources: + datasource.attach_index(index_config) + + index: SubstrateIndex[Any, Any, Any] + if isinstance(index_config, SubstrateEventsIndexConfig): + index = SubstrateEventsIndex(self, index_config, index_datasources) + else: + raise NotImplementedError + + return index + def _create_tezos_index(self, index_config: TezosIndexConfig) -> TezosIndex[Any, Any]: datasources = tuple(self.get_tezos_tzkt_datasource(c.name) for c in index_config.datasources) @@ -499,6 +524,12 @@ def get_starknet_datasource(self, name: str) -> StarknetSubsquidDatasource | Sta """Get `starknet` datasource by name""" return self._get_datasource(name, StarknetSubsquidDatasource, StarknetNodeDatasource) # type: ignore[return-value] + def get_substrate_datasource( + self, name: str + ) -> SubstrateSubsquidDatasource | SubstrateSubscanDatasource | SubstrateNodeDatasource: + """Get `substrate` datasource by name""" + return self._get_datasource(name, SubstrateSubsquidDatasource, SubstrateSubscanDatasource, SubstrateNodeDatasource) # type: ignore[return-value] + def get_coinbase_datasource(self, name: str) -> CoinbaseDatasource: """Get `coinbase` datasource by name diff --git a/src/dipdup/datasources/__init__.py b/src/dipdup/datasources/__init__.py index f84359891..506185266 100644 --- a/src/dipdup/datasources/__init__.py +++ b/src/dipdup/datasources/__init__.py @@ -1,18 +1,29 @@ +import asyncio +import time from abc import abstractmethod from collections.abc import Awaitable from collections.abc import Callable from typing import Any from typing import Generic from typing import TypeVar +from uuid import uuid4 + +from pysignalr.messages import CompletionMessage from dipdup.config import DatasourceConfig from dipdup.config import HttpConfig from dipdup.config import IndexConfig from dipdup.config import IndexDatasourceConfig from dipdup.config import ResolvedHttpConfig +from dipdup.exceptions import DatasourceError from dipdup.exceptions import FrameworkException from dipdup.http import HTTPGateway from dipdup.models import MessageType +from dipdup.performance import metrics +from dipdup.pysignalr import Message +from dipdup.pysignalr import WebsocketMessage +from dipdup.pysignalr import WebsocketProtocol +from dipdup.pysignalr import WebsocketTransport from dipdup.subscriptions import Subscription from dipdup.subscriptions import SubscriptionManager from dipdup.utils import FormattedLogger @@ -24,30 +35,6 @@ RollbackCallback = Callable[['IndexDatasource[Any]', MessageType, int, int], Awaitable[None]] -class EvmHistoryProvider: - pass - - -class EvmRealtimeProvider: - pass - - -class EvmAbiProvider: - pass - - -class TezosHistoryProvider: - pass - - -class TezosRealtimeProvider: - pass - - -class TezosAbiProvider: - pass - - class Datasource(HTTPGateway, Generic[DatasourceConfigT]): _default_http_config = HttpConfig() @@ -128,6 +115,111 @@ async def emit_rollback(self, type_: MessageType, from_level: int, to_level: int await fn(self, type_, from_level, to_level) +# FIXME: Not necessary a index datasource +class WebsocketDatasource(IndexDatasource[IndexDatasourceConfigT]): + def __init__(self, config: IndexDatasourceConfigT) -> None: + super().__init__(config) + self._ws_client: WebsocketTransport | None = None + + @abstractmethod + async def _on_message(self, message: Message) -> None: ... + + async def _on_error(self, message: CompletionMessage) -> None: + raise DatasourceError(f'RPC error: {message}', self.name) + + async def _on_connected(self) -> None: + self._logger.info('Realtime connection established') + # NOTE: Subscribing here will block WebSocket loop, don't do it. + await self.emit_connected() + + async def _on_disconnected(self) -> None: + self._logger.info('Realtime connection lost, resetting subscriptions') + self._subscriptions.reset() + await self.emit_disconnected() + + def _get_ws_client(self) -> WebsocketTransport: + if self._ws_client: + return self._ws_client + + self._logger.debug('Creating Websocket client') + + # FIXME: correct config class + url = self._config.ws_url # type: ignore + if not url: + raise FrameworkException('Spawning node datasource, but `ws_url` is not set') + self._ws_client = WebsocketTransport( + url=url, + protocol=WebsocketProtocol(), + callback=self._on_message, + skip_negotiation=True, + connection_timeout=self._http_config.connection_timeout, + ) + + self._ws_client.on_open(self._on_connected) + self._ws_client.on_close(self._on_disconnected) + self._ws_client.on_error(self._on_error) + + return self._ws_client + + +# FIXME: Not necessary a WS datasource +class JsonRpcDatasource(WebsocketDatasource[IndexDatasourceConfigT]): + def __init__(self, config: IndexDatasourceConfigT) -> None: + super().__init__(config) + self._requests: dict[str, tuple[asyncio.Event, Any]] = {} + + async def _jsonrpc_request( + self, + method: str, + params: Any, + raw: bool = False, + ws: bool = False, + ) -> Any: + request_id = uuid4().hex + request = { + 'jsonrpc': '2.0', + 'id': request_id, + 'method': method, + 'params': params, + } + self._logger.debug('JSON-RPC request: %s', request) + + if ws: + started_at = time.time() + event = asyncio.Event() + self._requests[request_id] = (event, None) + + message = WebsocketMessage(request) + client = self._get_ws_client() + + async def _request() -> None: + await client.send(message) + await event.wait() + + await asyncio.wait_for( + _request(), + timeout=self._http_config.request_timeout, + ) + data = self._requests[request_id][1] + del self._requests[request_id] + + metrics.time_in_requests[self.name] += time.time() - started_at + metrics.requests_total[self.name] += 1 + else: + data = await self.request( + method='post', + url='', + json=request, + ) + + if raw: + return data + + if 'error' in data: + raise DatasourceError(data['error']['message'], self.name) + return data['result'] + + def create_datasource(config: DatasourceConfig) -> Datasource[Any]: from dipdup.config.abi_etherscan import AbiEtherscanDatasourceConfig from dipdup.config.coinbase import CoinbaseDatasourceConfig @@ -137,6 +229,9 @@ def create_datasource(config: DatasourceConfig) -> Datasource[Any]: from dipdup.config.ipfs import IpfsDatasourceConfig from dipdup.config.starknet_node import StarknetNodeDatasourceConfig from dipdup.config.starknet_subsquid import StarknetSubsquidDatasourceConfig + from dipdup.config.substrate_node import SubstrateNodeDatasourceConfig + from dipdup.config.substrate_subscan import SubstrateSubscanDatasourceConfig + from dipdup.config.substrate_subsquid import SubstrateSubsquidDatasourceConfig from dipdup.config.tezos_tzkt import TezosTzktDatasourceConfig from dipdup.config.tzip_metadata import TzipMetadataDatasourceConfig from dipdup.datasources.abi_etherscan import AbiEtherscanDatasource @@ -147,6 +242,9 @@ def create_datasource(config: DatasourceConfig) -> Datasource[Any]: from dipdup.datasources.ipfs import IpfsDatasource from dipdup.datasources.starknet_node import StarknetNodeDatasource from dipdup.datasources.starknet_subsquid import StarknetSubsquidDatasource + from dipdup.datasources.substrate_node import SubstrateNodeDatasource + from dipdup.datasources.substrate_subscan import SubstrateSubscanDatasource + from dipdup.datasources.substrate_subsquid import SubstrateSubsquidDatasource from dipdup.datasources.tezos_tzkt import TezosTzktDatasource from dipdup.datasources.tzip_metadata import TzipMetadataDatasource @@ -161,6 +259,9 @@ def create_datasource(config: DatasourceConfig) -> Datasource[Any]: EvmNodeDatasourceConfig: EvmNodeDatasource, StarknetSubsquidDatasourceConfig: StarknetSubsquidDatasource, StarknetNodeDatasourceConfig: StarknetNodeDatasource, + SubstrateSubsquidDatasourceConfig: SubstrateSubsquidDatasource, + SubstrateSubscanDatasourceConfig: SubstrateSubscanDatasource, + SubstrateNodeDatasourceConfig: SubstrateNodeDatasource, } try: diff --git a/src/dipdup/datasources/abi_etherscan.py b/src/dipdup/datasources/abi_etherscan.py index 72181aa4d..196e2b559 100644 --- a/src/dipdup/datasources/abi_etherscan.py +++ b/src/dipdup/datasources/abi_etherscan.py @@ -10,11 +10,10 @@ from dipdup.config.abi_etherscan import AbiEtherscanDatasourceConfig from dipdup.datasources import AbiDatasource from dipdup.datasources import Datasource -from dipdup.datasources import EvmAbiProvider from dipdup.exceptions import DatasourceError -class AbiEtherscanDatasource(AbiDatasource[AbiEtherscanDatasourceConfig], EvmAbiProvider): +class AbiEtherscanDatasource(AbiDatasource[AbiEtherscanDatasourceConfig]): _default_http_config = HttpConfig( ratelimit_rate=1, ratelimit_period=5, diff --git a/src/dipdup/datasources/evm_node.py b/src/dipdup/datasources/evm_node.py index 8f0736cd2..44b8517cc 100644 --- a/src/dipdup/datasources/evm_node.py +++ b/src/dipdup/datasources/evm_node.py @@ -9,17 +9,13 @@ from dataclasses import field from typing import TYPE_CHECKING from typing import Any -from uuid import uuid4 import pysignalr import pysignalr.exceptions -from pysignalr.messages import CompletionMessage from dipdup.config import HttpConfig from dipdup.config.evm_node import EvmNodeDatasourceConfig -from dipdup.datasources import EvmHistoryProvider -from dipdup.datasources import EvmRealtimeProvider -from dipdup.datasources import IndexDatasource +from dipdup.datasources import JsonRpcDatasource from dipdup.datasources._web3 import create_web3_client from dipdup.exceptions import DatasourceError from dipdup.exceptions import FrameworkException @@ -32,10 +28,8 @@ from dipdup.models.evm_node import EvmNodeSubscription from dipdup.models.evm_node import EvmNodeSyncingData from dipdup.models.evm_node import EvmNodeSyncingSubscription -from dipdup.performance import metrics from dipdup.pysignalr import Message from dipdup.pysignalr import WebsocketMessage -from dipdup.pysignalr import WebsocketProtocol from dipdup.pysignalr import WebsocketTransport from dipdup.utils import Watchdog @@ -73,7 +67,7 @@ async def wait_level(self) -> None: await asyncio.sleep(to_wait) -class EvmNodeDatasource(IndexDatasource[EvmNodeDatasourceConfig], EvmHistoryProvider, EvmRealtimeProvider): +class EvmNodeDatasource(JsonRpcDatasource[EvmNodeDatasourceConfig]): _default_http_config = HttpConfig( batch_size=10, ratelimit_sleep=1, @@ -81,7 +75,7 @@ class EvmNodeDatasource(IndexDatasource[EvmNodeDatasourceConfig], EvmHistoryProv ) def __init__(self, config: EvmNodeDatasourceConfig, merge_subscriptions: bool = False) -> None: - super().__init__(config, merge_subscriptions) + super().__init__(config) self._web3_client: AsyncWeb3 | None = None self._ws_client: WebsocketTransport | None = None self._requests: dict[str, tuple[asyncio.Event, Any]] = {} @@ -254,56 +248,6 @@ async def _subscribe(self, subscription: EvmNodeSubscription) -> None: level = await self.get_head_level() self._subscriptions.set_sync_level(subscription, level) - async def _jsonrpc_request( - self, - method: str, - params: Any, - raw: bool = False, - ws: bool = False, - ) -> Any: - request_id = uuid4().hex - request = { - 'jsonrpc': '2.0', - 'id': request_id, - 'method': method, - 'params': params, - } - - if ws: - started_at = time.time() - event = asyncio.Event() - self._requests[request_id] = (event, None) - - message = WebsocketMessage(request) - client = self._get_ws_client() - - async def _request() -> None: - await client.send(message) - await event.wait() - - await asyncio.wait_for( - _request(), - timeout=self._http_config.request_timeout, - ) - data = self._requests[request_id][1] - del self._requests[request_id] - - metrics.time_in_requests[self.name] += time.time() - started_at - metrics.requests_total[self.name] += 1 - else: - data = await self.request( - method='post', - url='', - json=request, - ) - - if raw: - return data - - if 'error' in data: - raise DatasourceError(data['error']['message'], self.name) - return data['result'] - async def _on_message(self, message: Message) -> None: # NOTE: pysignalr will eventually get a raw client if not isinstance(message, WebsocketMessage): @@ -349,39 +293,3 @@ async def _handle_subscription(self, subscription: EvmNodeSubscription, data: An await self.emit_syncing(syncing) else: raise NotImplementedError - - async def _on_error(self, message: CompletionMessage) -> None: - raise DatasourceError(f'Node error: {message}', self.name) - - async def _on_connected(self) -> None: - self._logger.info('Realtime connection established') - # NOTE: Subscribing here will block WebSocket loop, don't do it. - await self.emit_connected() - - async def _on_disconnected(self) -> None: - self._logger.info('Realtime connection lost, resetting subscriptions') - self._subscriptions.reset() - await self.emit_disconnected() - - def _get_ws_client(self) -> WebsocketTransport: - if self._ws_client: - return self._ws_client - - self._logger.debug('Creating Websocket client') - - url = self._config.ws_url - if not url: - raise FrameworkException('Spawning node datasource, but `ws_url` is not set') - self._ws_client = WebsocketTransport( - url=url, - protocol=WebsocketProtocol(), - callback=self._on_message, - skip_negotiation=True, - connection_timeout=self._http_config.connection_timeout, - ) - - self._ws_client.on_open(self._on_connected) - self._ws_client.on_close(self._on_disconnected) - self._ws_client.on_error(self._on_error) - - return self._ws_client diff --git a/src/dipdup/datasources/evm_subsquid.py b/src/dipdup/datasources/evm_subsquid.py index 898908e13..4263a5e12 100644 --- a/src/dipdup/datasources/evm_subsquid.py +++ b/src/dipdup/datasources/evm_subsquid.py @@ -4,7 +4,6 @@ from typing import Any from dipdup.config.evm_subsquid import EvmSubsquidDatasourceConfig -from dipdup.datasources import EvmHistoryProvider from dipdup.datasources._subsquid import AbstractSubsquidDatasource from dipdup.datasources._subsquid import AbstractSubsquidWorker from dipdup.models.evm import EvmEventData @@ -64,7 +63,7 @@ class _EvmSubsquidWorker(AbstractSubsquidWorker[Query]): pass -class EvmSubsquidDatasource(AbstractSubsquidDatasource[EvmSubsquidDatasourceConfig, Query], EvmHistoryProvider): +class EvmSubsquidDatasource(AbstractSubsquidDatasource[EvmSubsquidDatasourceConfig, Query]): def __init__(self, config: EvmSubsquidDatasourceConfig) -> None: super().__init__(config) diff --git a/src/dipdup/datasources/substrate_node.py b/src/dipdup/datasources/substrate_node.py new file mode 100644 index 000000000..b61d33481 --- /dev/null +++ b/src/dipdup/datasources/substrate_node.py @@ -0,0 +1,207 @@ +import asyncio +import logging +import math +from collections.abc import AsyncIterator +from copy import copy +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from typing import Any + +import orjson + +from dipdup.config import HttpConfig +from dipdup.config.substrate import SubstrateDatasourceConfigU +from dipdup.datasources import JsonRpcDatasource +from dipdup.pysignalr import Message + +_logger = logging.getLogger(__name__) + + +@dataclass +class MetadataVersion: + spec_name: str + spec_version: int + block_number: int + block_hash: str + metadata: str | None = None + + @property + def key(self) -> str: + return f'{self.spec_name}@{self.spec_version}' + + +type MetadataHeader = MetadataVersion + + +def equal_specs(a: MetadataVersion, b: MetadataVersion) -> bool: + return a.spec_name == b.spec_name and a.spec_version == b.spec_version + + +@dataclass +class MetadataStorage: + path: Path + versions: list[MetadataVersion] = field(default_factory=list) + + def load_file(self) -> None: + if self.path.name.endswith('.jsonl'): + self.versions = [] + for line in self.path.read_text().splitlines(): + if not line: + continue + version = MetadataVersion(**orjson.loads(line)) + self.versions.append(version) + elif self.path.name.endswith('.json'): + self.versions = [MetadataVersion(**i) for i in orjson.loads(self.path.read_bytes())] + else: + raise ValueError(f'Unsupported file type: {self.path}') + + def save_file(self) -> None: + if self.path.name.endswith('.jsonl'): + self.path.write_bytes(b'\n'.join(orjson.dumps(version.__dict__) for version in self.versions)) + elif self.path.name.endswith('.json'): + self.path.write_bytes(orjson.dumps(self.versions)) + else: + raise ValueError(f'Unsupported file type: {self.path}') + + +class SubstrateNodeDatasource(JsonRpcDatasource[SubstrateDatasourceConfigU]): + _default_http_config = HttpConfig( + batch_size=20, + ) + + async def run(self) -> None: + pass + + async def initialize(self) -> None: + pass + + async def subscribe(self) -> None: + pass + + async def _on_message(self, message: Message) -> None: + raise NotImplementedError + + async def get_height(self) -> int: + head = await self._jsonrpc_request('chain_getFinalizedHead', []) + header = await self._jsonrpc_request('chain_getHeader', [head]) + return int(header['number'], 16) + + async def get_metadata_header(self, height: int) -> MetadataHeader: + block_hash = await self._jsonrpc_request('chain_getBlockHash', [height]) + rt = await self._jsonrpc_request('chain_getRuntimeVersion', [block_hash]) + return MetadataHeader( + spec_name=rt['specName'], + spec_version=rt['specVersion'], + block_number=height, + block_hash=block_hash, + ) + + async def get_metadata_header_batch(self, heights: list[int]) -> list[MetadataHeader]: + return await asyncio.gather(*[self.get_metadata_header(h) for h in heights]) + + async def find_metadata_versions( + self, + from_block: int | None = None, + to_block: int | None = None, + ) -> AsyncIterator[MetadataHeader]: + height = await self.get_height() + + first_block = from_block or 0 + last_block = min(to_block, height) if to_block is not None else height + if first_block > last_block: + return iter([]) + + queue: list[tuple[MetadataVersion, MetadataVersion]] = [] + versions: dict[str, MetadataVersion] = {} + + beg, end = await self.get_metadata_header_batch([from_block, last_block]) + versions[beg.key] = beg + + if not equal_specs(beg, end): + versions[end.key] = end + queue.append((beg, end)) + + step = 0 + while queue: + batch = queue[: self._http_config.batch_size] + queue = queue[self._http_config.batch_size :] + + step += 1 + _logger.info('step %s, %s versions found so far', step, len(versions)) + + heights = [b.block_number + math.floor((e.block_number - b.block_number) / 2) for b, e in batch] + new_versions = await self.get_metadata_header_batch(heights) + for (b, e), m in zip(batch, new_versions, strict=False): + if not equal_specs(b, m): + versions[m.key] = m + if not equal_specs(b, m) and m.block_number - b.block_number > 1: + queue.append((b, m)) + if not equal_specs(m, e) and e.block_number - m.block_number > 1: + queue.append((m, e)) + + return sorted(versions.values(), key=lambda x: x.block_number) + + async def get_metadata_version(self, block_hash: str) -> dict[str, Any]: + return await self._jsonrpc_request('state_getMetadata', [block_hash]) + + async def get_dev_metadata_version(self) -> MetadataVersion | None: + genesis = await self.get_metadata_header(0) + height = await self.get_height() + last = await self.get_metadata_header(height) + if genesis == last: + return genesis + return None + + +# FIXME: Not used, should be a subscan replacement +async def fetch_metadata( + datasource: SubstrateNodeDatasource, + storage: MetadataStorage, + from_block: int | None = None, + to_block: int | None = None, +) -> None: + matched = 0 + for version in storage.versions: + _logger.info('checking %s block %s against current chain', version.key, version.block_number) + current = await datasource.get_metadata_header(version.block_number) + if current and current.block_hash and version.block_hash.startswith(current.block_hash): + matched += 1 + else: + _logger.info('record mismatch') + break + + if matched > 0: + if matched != len(storage.versions): + storage.versions = storage.versions[:matched] + storage.save_file() + last_known = storage.versions[-1] + from_block = max(last_known.block_number, from_block or 0) + _logger.info('exploring chain from block %s, from_block') + new_versions = (await datasource.find_metadata_versions(from_block, to_block))[1:] + _logger.info('%s new versions found', len(new_versions)) + elif not storage.versions: + from_block = from_block or 0 + _logger.info('exploring chain from block %s', from_block) + new_versions = await datasource.find_metadata_versions(from_block, to_block) + _logger.info('%s new versions found', len(new_versions)) + else: + last_known = storage.versions[-1] + new_version = await datasource.get_dev_metadata_version() + if new_version is None or ( + new_version.spec_name == last_known.spec_name and last_known.spec_version > new_version.spec_version + ): + raise ValueError("Output file already contains data for a different chain, don't know how to proceed.") + if new_version.spec_name == last_known.spec_name and new_version.spec_version == last_known.spec_version: + _logger.info('replacing metadata for %s, assuming it came from dev runtime', last_known.key) + storage.versions = storage.versions[:-1] + storage.save_file() + new_versions = [new_version] + + for header in new_versions: + version = copy(header) + version.metadata = (await datasource.get_metadata_version(version.block_hash),) + storage.versions.append(version) + _logger.info('saved %s block %s', version.key, version.block_number) + + storage.save_file() diff --git a/src/dipdup/datasources/substrate_subscan.py b/src/dipdup/datasources/substrate_subscan.py new file mode 100644 index 000000000..acd92c6de --- /dev/null +++ b/src/dipdup/datasources/substrate_subscan.py @@ -0,0 +1,28 @@ +from typing import Any +from typing import cast + +from dipdup.config.substrate_subscan import SubstrateSubscanDatasourceConfig +from dipdup.datasources import AbiDatasource + + +class SubstrateSubscanDatasource(AbiDatasource[SubstrateSubscanDatasourceConfig]): + async def get_abi(self, address: str) -> dict[str, Any]: + raise NotImplementedError + + async def run(self) -> None: + pass + + async def get_runtime_list(self) -> list[dict[str, Any]]: + res = await self.request( + 'post', + 'scan/runtime/list', + ) + return cast(list[dict[str, Any]], res['data']['list']) + + async def get_runtime_metadata(self, spec_version: int) -> dict[str, Any]: + res = await self.request( + 'post', + 'scan/runtime/metadata', + json={'spec': spec_version}, + ) + return cast(dict[str, Any], res['data']['info']['metadata']) diff --git a/src/dipdup/datasources/substrate_subsquid.py b/src/dipdup/datasources/substrate_subsquid.py new file mode 100644 index 000000000..4f64c3d0c --- /dev/null +++ b/src/dipdup/datasources/substrate_subsquid.py @@ -0,0 +1,54 @@ +from collections.abc import AsyncIterator + +from dipdup.config.substrate_subsquid import SubstrateSubsquidDatasourceConfig +from dipdup.datasources._subsquid import AbstractSubsquidDatasource +from dipdup.models._subsquid import AbstractSubsquidQuery +from dipdup.models.substrate import SubstrateEventData + +Query = AbstractSubsquidQuery + + +class SubstrateSubsquidDatasource(AbstractSubsquidDatasource[SubstrateSubsquidDatasourceConfig, Query]): + async def iter_events( + self, + first_level: int, + last_level: int, + names: tuple[str, ...], + ) -> AsyncIterator[tuple[SubstrateEventData, ...]]: + current_level = first_level + + while current_level <= last_level: + query: Query = { # type: ignore[typeddict-unknown-key] + 'fields': { + 'event': { + 'name': True, + 'args': True, + }, + 'block': { + 'hash': True, + 'parentHash': True, + 'stateRoot': True, + 'extrinsicsRoot': True, + 'digest': True, + 'specName': True, + 'specVersion': True, + 'implName': True, + 'implVersion': True, + 'timestamp': True, + 'validator': True, + }, + }, + 'events': [ + { + 'name': list(names), + }, + ], + 'fromBlock': current_level, + 'toBlock': last_level, + 'type': 'substrate', + } + response = await self.query_worker(query, current_level) + + for level_item in response: + yield tuple(SubstrateEventData(**e, header=level_item['header']) for e in level_item['events']) + current_level = level_item['header']['number'] + 1 diff --git a/src/dipdup/datasources/tezos_tzkt.py b/src/dipdup/datasources/tezos_tzkt.py index cc76db0dc..3dd9b1bc1 100644 --- a/src/dipdup/datasources/tezos_tzkt.py +++ b/src/dipdup/datasources/tezos_tzkt.py @@ -29,9 +29,6 @@ from dipdup.config.tezos_tzkt import TezosTzktDatasourceConfig from dipdup.datasources import Datasource from dipdup.datasources import IndexDatasource -from dipdup.datasources import TezosAbiProvider -from dipdup.datasources import TezosHistoryProvider -from dipdup.datasources import TezosRealtimeProvider from dipdup.exceptions import DatasourceError from dipdup.exceptions import FrameworkException from dipdup.models import Head @@ -234,9 +231,7 @@ def get_address(self, code_hash: int, type_hash: int) -> str: return self._hashes_to_address[(code_hash, type_hash)] -class TezosTzktDatasource( - IndexDatasource[TezosTzktDatasourceConfig], TezosHistoryProvider, TezosRealtimeProvider, TezosAbiProvider -): +class TezosTzktDatasource(IndexDatasource[TezosTzktDatasourceConfig]): _default_http_config = HttpConfig( retry_sleep=1, retry_multiplier=1.1, diff --git a/src/dipdup/dipdup.py b/src/dipdup/dipdup.py index 24c2dd1fe..a375677c6 100644 --- a/src/dipdup/dipdup.py +++ b/src/dipdup/dipdup.py @@ -315,9 +315,9 @@ def _log_status(self) -> None: if not progress: if self._indexes: if scanned_levels: - msg = f'indexing: {scanned_levels:6} levels, estimating...' - elif objects_indexed := int(metrics.objects_indexed): - msg = f'indexing: {objects_indexed:6} objects, estimating...' + msg = f'indexing: {scanned_levels} levels, estimating...' + elif metrics.objects_indexed: + msg = f'indexing: {metrics.objects_indexed} objects, estimating...' else: msg = 'indexing: warming up...' else: @@ -635,6 +635,7 @@ async def init( """Create new or update existing dipdup project""" from dipdup.codegen.evm import EvmCodeGenerator from dipdup.codegen.starknet import StarknetCodeGenerator + from dipdup.codegen.substrate import SubstrateCodeGenerator from dipdup.codegen.tezos import TezosCodeGenerator await self._create_datasources() @@ -648,9 +649,10 @@ async def init( codegen_classes: tuple[type[CodeGenerator], ...] = ( # type: ignore[assignment] CommonCodeGenerator, - TezosCodeGenerator, EvmCodeGenerator, StarknetCodeGenerator, + SubstrateCodeGenerator, + TezosCodeGenerator, ) for codegen_cls in codegen_classes: codegen = codegen_cls( diff --git a/src/dipdup/http.py b/src/dipdup/http.py index c95a014bf..943085905 100644 --- a/src/dipdup/http.py +++ b/src/dipdup/http.py @@ -11,6 +11,7 @@ from typing import Any from typing import Literal from typing import overload +from urllib.parse import parse_qsl from urllib.parse import urlsplit from urllib.parse import urlunsplit @@ -80,6 +81,7 @@ def __init__(self, url: str, config: ResolvedHttpConfig) -> None: self._url = urlunsplit((parsed_url.scheme, parsed_url.netloc, '', '', '')) self._alias = config.alias or parsed_url.netloc self._path = parsed_url.path + self._query = parsed_url.query self._config = config self._user_agent_args: tuple[str, ...] = () self._user_agent: str | None = None @@ -223,7 +225,10 @@ async def _request( headers = kwargs.pop('headers', {}) headers['User-Agent'] = self.user_agent - params = kwargs.get('params', {}) + params = kwargs.pop('params', {}) + for item in parse_qsl(self._query): + params[item[0]] = item[1] + params_string = '&'.join(f'{k}={v}' for k, v in params.items()) request_string = f'{self._url}{url}?{params_string}'.rstrip('?') self._logger.debug('Calling `%s`', request_string) @@ -238,6 +243,7 @@ async def _request( url=url, headers=headers, raise_for_status=True, + params=params, **kwargs, ) as response: await response.read() diff --git a/src/dipdup/indexes/evm.py b/src/dipdup/indexes/evm.py index 7bb86e1a4..13bac690f 100644 --- a/src/dipdup/indexes/evm.py +++ b/src/dipdup/indexes/evm.py @@ -49,6 +49,7 @@ def get_sighash( raise ConfigurationError('Either `to` or `signature` filters are expected') +# FIXME: Should be subsquid one class EvmIndex( Generic[IndexConfigT, IndexQueueItemT, DatasourceT], Index[IndexConfigT, IndexQueueItemT, DatasourceT], diff --git a/src/dipdup/indexes/starknet_events/__init__.py b/src/dipdup/indexes/starknet_events/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/dipdup/indexes/substrate.py b/src/dipdup/indexes/substrate.py new file mode 100644 index 000000000..4dbf6581f --- /dev/null +++ b/src/dipdup/indexes/substrate.py @@ -0,0 +1,39 @@ +from abc import ABC +from typing import TYPE_CHECKING +from typing import Generic +from typing import TypeVar + +from dipdup.config import SubstrateIndexConfigU +from dipdup.datasources.substrate_node import SubstrateNodeDatasource +from dipdup.datasources.substrate_subscan import SubstrateSubscanDatasource +from dipdup.datasources.substrate_subsquid import SubstrateSubsquidDatasource +from dipdup.index import IndexQueueItemT +from dipdup.indexes.evm import EvmIndex +from dipdup.runtimes import SubstrateRuntime + +SubstrateDatasource = SubstrateSubsquidDatasource | SubstrateSubscanDatasource | SubstrateNodeDatasource + +IndexConfigT = TypeVar('IndexConfigT', bound=SubstrateIndexConfigU) +DatasourceT = TypeVar('DatasourceT', bound=SubstrateDatasource) + +if TYPE_CHECKING: + from dipdup.context import DipDupContext + + +class SubstrateIndex( + Generic[IndexConfigT, IndexQueueItemT, DatasourceT], + # FIXME: it's not + EvmIndex[IndexConfigT, IndexQueueItemT, DatasourceT], + ABC, +): + def __init__( + self, + ctx: 'DipDupContext', + config: IndexConfigT, + datasources: tuple[DatasourceT, ...], + ) -> None: + super().__init__(ctx, config, datasources) + self.runtime = SubstrateRuntime( + name=config.runtime.name, + package=ctx.package, + ) diff --git a/src/dipdup/indexes/substrate_events/fetcher.py b/src/dipdup/indexes/substrate_events/fetcher.py new file mode 100644 index 000000000..4cef2ea9f --- /dev/null +++ b/src/dipdup/indexes/substrate_events/fetcher.py @@ -0,0 +1,32 @@ +from collections.abc import AsyncIterator + +from dipdup.datasources.substrate_subsquid import SubstrateSubsquidDatasource +from dipdup.indexes.substrate_subsquid import SubstrateSubsquidFetcher +from dipdup.models.substrate import SubstrateEventData + + +class SubstrateSubsquidEventFetcher(SubstrateSubsquidFetcher[SubstrateEventData]): + def __init__( + self, + name: str, + datasources: tuple[SubstrateSubsquidDatasource, ...], + first_level: int, + last_level: int, + names: tuple[str, ...], + ) -> None: + super().__init__( + name=name, + datasources=datasources, + first_level=first_level, + last_level=last_level, + ) + self._names = names + + async def fetch_by_level(self) -> AsyncIterator[tuple[int, tuple[SubstrateEventData, ...]]]: + event_iter = self.random_datasource.iter_events( + first_level=self._first_level, + last_level=self._last_level, + names=self._names, + ) + async for level, batch in self.readahead_by_level(event_iter): + yield level, batch diff --git a/src/dipdup/indexes/substrate_events/index.py b/src/dipdup/indexes/substrate_events/index.py new file mode 100644 index 000000000..08a3caab7 --- /dev/null +++ b/src/dipdup/indexes/substrate_events/index.py @@ -0,0 +1,83 @@ +from collections import deque +from collections.abc import Iterable +from typing import TYPE_CHECKING +from typing import Any + +from dipdup.config.substrate_events import SubstrateEventsHandlerConfig +from dipdup.config.substrate_events import SubstrateEventsIndexConfig +from dipdup.datasources.substrate_subsquid import SubstrateSubsquidDatasource +from dipdup.indexes.substrate import SubstrateDatasource +from dipdup.indexes.substrate import SubstrateIndex +from dipdup.indexes.substrate_events.fetcher import SubstrateSubsquidEventFetcher +from dipdup.models import RollbackMessage +from dipdup.models._subsquid import SubsquidMessageType +from dipdup.models.substrate import SubstrateEvent +from dipdup.models.substrate import SubstrateEventData +from dipdup.performance import metrics + +QueueItem = tuple[SubstrateEventData, ...] | RollbackMessage +MatchedEventsT = tuple[SubstrateEventsHandlerConfig, SubstrateEvent[Any]] + +if TYPE_CHECKING: + from dipdup.context import DipDupContext + + +class SubstrateEventsIndex( + SubstrateIndex[SubstrateEventsIndexConfig, QueueItem, SubstrateDatasource], + # FIXME: stub message type + message_type=SubsquidMessageType.logs, +): + def __init__( + self, + ctx: 'DipDupContext', + config: SubstrateEventsIndexConfig, + datasources: tuple[SubstrateDatasource, ...], + ) -> None: + super().__init__(ctx, config, datasources) + self._names = tuple(c.name for c in self._config.handlers) + # FIXME: it's not EVM index + self.subsquid_datasources = tuple(d for d in datasources if isinstance(d, SubstrateSubsquidDatasource)) + + async def _synchronize_subsquid(self, sync_level: int) -> None: + first_level = self.state.level + 1 + fetcher = self._create_subsquid_fetcher(first_level, sync_level) + + async for _level, events in fetcher.fetch_by_level(): + await self._process_level_data(tuple(events), sync_level) + metrics._sqd_processor_last_block = int(_level) + + async def _synchronize_node(self, sync_level: int) -> None: + raise NotImplementedError + + def _create_subsquid_fetcher(self, first_level: int, last_level: int) -> SubstrateSubsquidEventFetcher: + + return SubstrateSubsquidEventFetcher( + name=self.name, + datasources=self.subsquid_datasources, + first_level=first_level, + last_level=last_level, + names=self._names, + ) + + def _match_level_data( + self, + handlers: tuple[SubstrateEventsHandlerConfig, ...], + level_data: Iterable[SubstrateEventData], + ) -> deque[Any]: + """Try to match event events with all index handlers.""" + matched_handlers: deque[MatchedEventsT] = deque() + + for event in level_data: + for handler_config in handlers: + if handler_config.name != event.name: + continue + + arg: SubstrateEvent[Any] = SubstrateEvent( + data=event, + runtime=self.runtime, + ) + + matched_handlers.append((handler_config, arg)) + break + + return matched_handlers diff --git a/src/dipdup/indexes/substrate_subsquid.py b/src/dipdup/indexes/substrate_subsquid.py new file mode 100644 index 000000000..fa75d8006 --- /dev/null +++ b/src/dipdup/indexes/substrate_subsquid.py @@ -0,0 +1,25 @@ +from abc import ABC +from typing import Generic + +from dipdup.datasources.substrate_subsquid import SubstrateSubsquidDatasource +from dipdup.fetcher import BufferT +from dipdup.fetcher import DataFetcher + +SUBSTRATE_SUBSQUID_READAHEAD_LIMIT = 10000 + + +class SubstrateSubsquidFetcher(Generic[BufferT], DataFetcher[BufferT, SubstrateSubsquidDatasource], ABC): + def __init__( + self, + name: str, + datasources: tuple[SubstrateSubsquidDatasource, ...], + first_level: int, + last_level: int, + ) -> None: + super().__init__( + name=name, + datasources=datasources, + first_level=first_level, + last_level=last_level, + readahead_limit=SUBSTRATE_SUBSQUID_READAHEAD_LIMIT, + ) diff --git a/src/dipdup/models/__init__.py b/src/dipdup/models/__init__.py index 8e7555a7d..91f7f5d22 100644 --- a/src/dipdup/models/__init__.py +++ b/src/dipdup/models/__init__.py @@ -59,6 +59,7 @@ class IndexType(Enum): tezos_token_transfers = 'tezos.token_transfers' tezos_token_balances = 'tezos.token_balances' starknet_events = 'starknet.events' + substrate_events = 'substrate.events' class MessageType: diff --git a/src/dipdup/models/evm.py b/src/dipdup/models/evm.py index 80eb2c1ce..e9a66db27 100644 --- a/src/dipdup/models/evm.py +++ b/src/dipdup/models/evm.py @@ -1,4 +1,3 @@ -from abc import ABC from dataclasses import dataclass from typing import Any from typing import Generic @@ -59,7 +58,7 @@ def from_subsquid_json(cls, event_json: dict[str, Any], header: dict[str, Any]) @dataclass(frozen=True) -class EvmTransactionData(HasLevel, ABC): +class EvmTransactionData(HasLevel): access_list: tuple[dict[str, Any], ...] | None block_hash: str chain_id: int | None diff --git a/src/dipdup/models/substrate.py b/src/dipdup/models/substrate.py new file mode 100644 index 000000000..5a4d6c711 --- /dev/null +++ b/src/dipdup/models/substrate.py @@ -0,0 +1,70 @@ +from dataclasses import dataclass +from functools import cached_property +from typing import Any +from typing import Generic +from typing import TypedDict +from typing import TypeVar +from typing import cast + +from pydantic import BaseModel + +from dipdup.fetcher import HasLevel +from dipdup.runtimes import SubstrateRuntime + + +class BlockHeader(TypedDict): + hash: str + number: int + daHeight: str + transactionsRoot: str + transactionsCount: int + messageReceiptCount: int + prevRoot: str + time: str + applicationHash: str + eventInboxRoot: str + consensusParametersVersion: int + stateTransitionBytecodeVersion: int + messageOutboxRoot: str + + +@dataclass(frozen=True) +class SubstrateEventData(HasLevel): + name: str + index: int + extrinsicIndex: int + callAddress: list[str] + args: list[Any] + header: BlockHeader + + @property + def level(self) -> int: # type: ignore[override] + return self.header['number'] + + +PayloadT = TypeVar('PayloadT', bound=BaseModel) + + +@dataclass(frozen=True) +class SubstrateEvent(Generic[PayloadT]): + data: SubstrateEventData + runtime: SubstrateRuntime + + @cached_property + def payload(self) -> PayloadT: + return cast( + PayloadT, + self.runtime.decode_event_args( + name=self.name, + args=self.data.args, + spec_version=self.data.header['specVersion'], + ), + ) + + @property + def level(self) -> int: + return self.data.level + + @property + def name(self) -> str: + return self.data.name diff --git a/src/dipdup/indexes/__init__.py b/src/dipdup/models/substrate_subsquid.py similarity index 100% rename from src/dipdup/indexes/__init__.py rename to src/dipdup/models/substrate_subsquid.py diff --git a/src/dipdup/package.py b/src/dipdup/package.py index 9c45f36d4..f5c1c57a4 100644 --- a/src/dipdup/package.py +++ b/src/dipdup/package.py @@ -79,6 +79,7 @@ def __init__(self, root: Path, quiet: bool = False) -> None: # NOTE: Shared directories; not a part of package self._xdg_shared_dir = Path.home() / '.local' / 'share' / 'dipdup' self.schemas = self._xdg_shared_dir / 'schemas' / self.name + self.abi_local = self._xdg_shared_dir / 'abi' / self.name # NOTE: Finally, internal in-memory stuff self._replay: Answers | None = None diff --git a/src/dipdup/project.py b/src/dipdup/project.py index 0695d73dd..fe40e074e 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -38,6 +38,7 @@ 'demo_evm_uniswap', ), 'starknet': ('demo_starknet_events',), + 'substrate': ('demo_substrate_events',), 'tezos': ( 'demo_tezos_auction', 'demo_tezos_dao', @@ -148,11 +149,13 @@ def template_from_terminal() -> tuple[str | None, DipDupSurveyConfig | None]: options=( 'EVM', 'Starknet', + 'Substrate', 'Tezos', ), comments=( 'EVM-compatible blockchains', 'Starknet', + 'Substrate [PREVIEW]', 'Tezos', ), default=0, @@ -338,7 +341,7 @@ def render_base( _render( answers=answers, template_path=Path(__file__).parent / 'templates' / 'replay.yaml.j2', - output_path=Path('configs') / 'replay.yaml', + output_path=get_package_path(answers['package']) / Path('configs') / 'replay.yaml', force=force, ) @@ -353,9 +356,10 @@ def _render_templates( from jinja2 import Template project_path = Path(__file__).parent / 'projects' / path - project_paths = project_path.glob('**/*.j2') + project_templates = set(project_path.glob('**/*.j2')) + project_files = set(project_path.glob('**/*')) - project_templates - for path in project_paths: + for path in project_templates: template_path = path.relative_to(Path(__file__).parent) relative_path = str(Path(*template_path.parts[2:]))[:-3] @@ -371,6 +375,16 @@ def _render_templates( output_path = Path(Template(str(output_path)).render(project=answers)) _render(answers, template_path, output_path, force) + # NOTE: If there are files without .j2 extension, just copy them + for path in project_files: + if path.is_dir(): + continue + output_path = Path( + get_package_path(answers['package']), + *path.relative_to(project_path).parts, + ) + write(output_path, path.read_bytes(), overwrite=force) + def _render(answers: Answers, template_path: Path, output_path: Path, force: bool) -> None: if output_path.exists() and not force: diff --git a/src/dipdup/projects/demo_substrate_events/configs/dipdup.assethub.yaml b/src/dipdup/projects/demo_substrate_events/configs/dipdup.assethub.yaml new file mode 100644 index 000000000..dc7b6d1d3 --- /dev/null +++ b/src/dipdup/projects/demo_substrate_events/configs/dipdup.assethub.yaml @@ -0,0 +1,27 @@ +runtimes: + assethub: + kind: substrate + +datasources: + subsquid: + kind: substrate.subsquid + url: https://v2.archive.subsquid.io/network/asset-hub-polkadot + subscan: + kind: substrate.subscan + url: https://assethub-polkadot.api.subscan.io/api + node: + kind: substrate.node + url: https://statemint.api.onfinality.io/rpc?apikey=${NODE_API_KEY:-''} + ws_url: wss://statemint.api.onfinality.io/ws?apikey=${NODE_API_KEY:-''} + +indexes: + assethub_transfers: + kind: substrate.events + runtime: assethub + datasources: + - subsquid + - subscan + - node + handlers: + - callback: on_assethub_transfer + name: Assets.Transferred \ No newline at end of file diff --git a/src/dipdup/projects/demo_substrate_events/configs/dipdup.kusama.yaml b/src/dipdup/projects/demo_substrate_events/configs/dipdup.kusama.yaml new file mode 100644 index 000000000..0c5af74f9 --- /dev/null +++ b/src/dipdup/projects/demo_substrate_events/configs/dipdup.kusama.yaml @@ -0,0 +1,27 @@ +runtimes: + kusama: + kind: substrate + +datasources: + subsquid: + kind: substrate.subsquid + url: https://v2.archive.subsquid.io/network/kusama + subscan: + kind: substrate.subscan + url: https://kusama.api.subscan.io/api + node: + kind: substrate.node + url: https://kusama.api.onfinality.io/rpc?apikey=${NODE_API_KEY:-''} + ws_url: wss://kusama.api.onfinality.io/ws?apikey=${NODE_API_KEY:-''} + +indexes: + kusama_transfers: + kind: substrate.events + runtime: kusama + datasources: + - subsquid + - subscan + - node + handlers: + - callback: on_kusama_transfer + name: Balances.Transfer \ No newline at end of file diff --git a/src/dipdup/projects/demo_substrate_events/dipdup.yaml.j2 b/src/dipdup/projects/demo_substrate_events/dipdup.yaml.j2 new file mode 100644 index 000000000..a79b49108 --- /dev/null +++ b/src/dipdup/projects/demo_substrate_events/dipdup.yaml.j2 @@ -0,0 +1,2 @@ +spec_version: 3.0 +package: {{ project.package }} diff --git a/src/dipdup/projects/demo_substrate_events/replay.yaml b/src/dipdup/projects/demo_substrate_events/replay.yaml new file mode 100644 index 000000000..8a79c588a --- /dev/null +++ b/src/dipdup/projects/demo_substrate_events/replay.yaml @@ -0,0 +1,5 @@ +spec_version: 3.0 +replay: + description: Substrate balance transfers [PREVIEW] + package: demo_substrate_events + template: demo_substrate_events \ No newline at end of file diff --git a/src/dipdup/runtimes.py b/src/dipdup/runtimes.py new file mode 100644 index 000000000..2e609ebae --- /dev/null +++ b/src/dipdup/runtimes.py @@ -0,0 +1,131 @@ +import logging +import re +from functools import cached_property +from typing import TYPE_CHECKING +from typing import Any + +import orjson + +from dipdup.exceptions import FrameworkException +from dipdup.package import DipDupPackage + +if TYPE_CHECKING: + from scalecodec.base import RuntimeConfigurationObject # type: ignore[import-untyped] + +_logger = logging.getLogger(__name__) + +ALIASES = { + 'assethub': 'statemint', +} + + +def extract_args_name(description: str) -> list[str]: + pattern = r'\((.*?)\)|\[(.*?)\]' + match = re.search(pattern, description) + + if not match: + raise ValueError('No valid bracket pairs found in the description') + + args_str = match.group(1) or match.group(2) + return [arg.strip('\\') for arg in args_str.split(', ')] + + +class SubstrateSpecVersion: + def __init__(self, name: str, metadata: list[dict[str, Any]]) -> None: + self._name = name + self._metadata = metadata + self._events: dict[str, dict[str, Any]] = {} + + def get_event_abi(self, qualname: str) -> dict[str, Any]: + if qualname not in self._events: + pallet, name = qualname.split('.') + found = False + for item in self._metadata: + # FIXME: double break + if found: + break + if item['name'] != pallet: + continue + for event in item.get('events', ()): + if event['name'] != name: + continue + self._events[qualname] = event + found = True + else: + raise FrameworkException(f'Event `{qualname}` not found in `{self._name}` spec') + + return self._events[qualname] + + +class SubstrateRuntime: + def __init__( + self, + name: str, + package: DipDupPackage, + ) -> None: + self._name = name + self._package = package + # TODO: unload by LRU? + self._spec_versions: dict[str, SubstrateSpecVersion] = {} + + @cached_property + def runtime_config(self) -> 'RuntimeConfigurationObject': + from scalecodec.base import RuntimeConfigurationObject + from scalecodec.type_registry import load_type_registry_preset # type: ignore[import-untyped] + + # FIXME: ss58_format + runtime_config = RuntimeConfigurationObject(ss58_format=99) + for name in ('core', ALIASES.get(self._name, self._name)): + preset = load_type_registry_preset(name) + assert preset + runtime_config.update_type_registry(preset) + + return runtime_config + + def get_spec_version(self, name: str) -> SubstrateSpecVersion: + if name not in self._spec_versions: + _logger.info('loading spec version `%s`', name) + metadata = orjson.loads(self._package.abi.joinpath(self._name, f'v{name}.json').read_bytes()) + self._spec_versions[name] = SubstrateSpecVersion( + name=f'v{name}', + metadata=metadata, + ) + + return self._spec_versions[name] + + def decode_event_args( + self, + name: str, + args: list[Any] | dict[str, Any], + spec_version: str, + ) -> dict[str, Any]: + from scalecodec.base import ScaleBytes + + spec_obj = self.get_spec_version(spec_version) + event_abi = spec_obj.get_event_abi( + qualname=name, + ) + + if isinstance(args, list): + assert 'args_name' not in event_abi + arg_names = extract_args_name(event_abi['docs'][0]) + args = dict(zip(arg_names, args, strict=True)) + else: + arg_names = event_abi['args_name'] + + arg_types = event_abi['args'] + + payload = {} + for (key, value), type_ in zip(args.items(), arg_types, strict=True): + if not isinstance(value, str) or not value.startswith('0x'): + payload[key] = value + continue + + scale_obj = self.runtime_config.create_scale_object( + type_string=type_, + data=ScaleBytes(value), + ) + scale_obj.decode() + payload[key] = scale_obj.value_serialized + + return payload diff --git a/src/dipdup/sentry.py b/src/dipdup/sentry.py index ec983586c..7cc1fea16 100644 --- a/src/dipdup/sentry.py +++ b/src/dipdup/sentry.py @@ -4,7 +4,6 @@ import platform from contextlib import suppress from typing import TYPE_CHECKING -from typing import Any import sentry_sdk import sentry_sdk.consts @@ -45,23 +44,6 @@ def extract_event(error: Exception) -> 'Event': return sentry_sdk.serializer.serialize(event) # type: ignore[arg-type,return-value] -def before_send( - event: 'Event', - hint: dict[str, Any], -) -> 'Event | None': - # NOTE: Dark magic ahead. Merge `CallbackError` and its cause when possible. - with suppress(KeyError, IndexError): - exceptions = event['exception']['values'] - if exceptions[-1]['type'] == 'CallbackError': - wrapper_frames = exceptions[-1]['stacktrace']['frames'] - crash_frames = exceptions[-2]['stacktrace']['frames'] - exceptions[-2]['stacktrace']['frames'] = wrapper_frames + crash_frames - event['message'] = exceptions[-2]['value'] - del exceptions[-1] - - return event - - def init_sentry(config: 'SentryConfig', package: str) -> None: dsn = config.dsn if dsn: @@ -102,7 +84,6 @@ def init_sentry(config: 'SentryConfig', package: str) -> None: dsn=dsn, integrations=integrations, attach_stacktrace=attach_stacktrace, - before_send=before_send, release=release, environment=environment, server_name=server_name, diff --git a/src/dipdup/utils.py b/src/dipdup/utils.py index 03831f871..1824bbb31 100644 --- a/src/dipdup/utils.py +++ b/src/dipdup/utils.py @@ -2,6 +2,7 @@ import importlib import logging import pkgutil +import re import types from collections import defaultdict from collections.abc import Callable @@ -255,3 +256,15 @@ async def run(self) -> None: except TimeoutError as e: msg = f'Watchdog timeout; no messages received in {self._timeout} seconds' raise FrameworkException(msg) from e + + +def sorted_glob(path: Path, pattern: str) -> list[Path]: + def natural_sort_key(item: Path) -> tuple[list[int | str], list[int | str]]: + def split_parts(text: str) -> list[int | str]: + parts = re.split(r'(\d+)', text) + return [int(part) if part.isdigit() else part.lower() for part in parts] + + # Sort by parent directories first, then by filename + return ([split_parts(part) for part in item.parent.parts], split_parts(item.name)) + + return sorted(path.glob(pattern), key=natural_sort_key)