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:
spec_version (ToStr) – Version of config specification, currently always 3.0
package (str) – Name of indexer’s Python package, existing or not
-datasources (dict[str, CoinbaseDatasourceConfig | AbiEtherscanDatasourceConfig | HttpDatasourceConfig | IpfsDatasourceConfig | EvmSubsquidDatasourceConfig | EvmNodeDatasourceConfig | TzipMetadataDatasourceConfig | TezosTzktDatasourceConfig | StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig]) – Mapping of datasource aliases and datasource configs
+datasources (dict[str, CoinbaseDatasourceConfig | AbiEtherscanDatasourceConfig | HttpDatasourceConfig | IpfsDatasourceConfig | EvmSubsquidDatasourceConfig | EvmNodeDatasourceConfig | TzipMetadataDatasourceConfig | TezosTzktDatasourceConfig | StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig | SubstrateSubsquidDatasourceConfig | SubstrateSubscanDatasourceConfig]) – Mapping of datasource aliases and datasource configs
database (SqliteDatabaseConfig | PostgresDatabaseConfig) – Database config
contracts (dict[str, EvmContractConfig | TezosContractConfig | StarknetContractConfig]) – Mapping of contract aliases and contract configs
-indexes (dict[str, TezosBigMapsIndexConfig | TezosEventsIndexConfig | TezosHeadIndexConfig | TezosOperationsIndexConfig | TezosOperationsUnfilteredIndexConfig | TezosTokenTransfersIndexConfig | TezosTokenBalancesIndexConfig | EvmEventsIndexConfig | EvmTransactionsIndexConfig | StarknetEventsIndexConfig | IndexTemplateConfig]) – Mapping of index aliases and index configs
-templates (dict[str, TezosBigMapsIndexConfig | TezosEventsIndexConfig | TezosHeadIndexConfig | TezosOperationsIndexConfig | TezosOperationsUnfilteredIndexConfig | TezosTokenTransfersIndexConfig | TezosTokenBalancesIndexConfig | EvmEventsIndexConfig | EvmTransactionsIndexConfig | StarknetEventsIndexConfig]) – Mapping of template aliases and index templates
+indexes (dict[str, TezosBigMapsIndexConfig | TezosEventsIndexConfig | TezosHeadIndexConfig | TezosOperationsIndexConfig | TezosOperationsUnfilteredIndexConfig | TezosTokenTransfersIndexConfig | TezosTokenBalancesIndexConfig | EvmEventsIndexConfig | EvmTransactionsIndexConfig | StarknetEventsIndexConfig | SubstrateEventsIndexConfig | IndexTemplateConfig]) – Mapping of index aliases and index configs
+templates (dict[str, TezosBigMapsIndexConfig | TezosEventsIndexConfig | TezosHeadIndexConfig | TezosOperationsIndexConfig | TezosOperationsUnfilteredIndexConfig | TezosTokenTransfersIndexConfig | TezosTokenBalancesIndexConfig | EvmEventsIndexConfig | EvmTransactionsIndexConfig | StarknetEventsIndexConfig | SubstrateEventsIndexConfig]) – Mapping of template aliases and index templates
jobs (dict[str, JobConfig]) – Mapping of job aliases and job configs
hooks (dict[str, HookConfig]) – Mapping of hook aliases and hook configs
hasura (HasuraConfig | None) – Hasura integration config
@@ -51,7 +51,7 @@ description: "Config file reference"
## dipdup.config.abi_etherscan.AbiEtherscanDatasourceConfig
-class dipdup.config.abi_etherscan.AbiEtherscanDatasourceConfig(kind, url='https://api.etherscan.io/api', api_key=None, http=None)
+class dipdup.config.abi_etherscan.AbiEtherscanDatasourceConfig(kind, url, api_key=None, http=None)
Etherscan datasource config
- Parameters:
@@ -1178,6 +1178,99 @@ description: "Config file reference"
+## dipdup.config.substrate_events.SubstrateEventsHandlerConfig
+
+class dipdup.config.substrate_events.SubstrateEventsHandlerConfig(callback, name)
+Subsquid event handler
+
+- Parameters:
+-
+
+
+
+
+
+
+## dipdup.config.substrate_events.SubstrateEventsIndexConfig
+
+class dipdup.config.substrate_events.SubstrateEventsIndexConfig(kind, datasources, typename=None, handlers, first_level=0, last_level=0)
+Subsquid datasource config
+
+- Parameters:
+-
+
+
+
+
+
+
+## dipdup.config.substrate.SubstrateIndexConfig
+
+class dipdup.config.substrate.SubstrateIndexConfig(kind, datasources, typename=None)
+EVM index that use Subsquid Network as a datasource
+
+- Parameters:
+-
+
+
+
+
+
+
+## dipdup.config.substrate_subsquid.SubstrateSubsquidDatasourceConfig
+
+class dipdup.config.substrate_subsquid.SubstrateSubsquidDatasourceConfig(kind, url, http=None)
+Subsquid datasource config
+
+- Parameters:
+
+kind (Literal['substrate.subsquid']) – always ‘substrate.subsquid’
+url (Url) – URL of Subsquid Network API
+http (HttpConfig | None) – HTTP client configuration
+
+
+
+
+
+
+
+
+## dipdup.config.substrate_subscan.SubstrateSubscanDatasourceConfig
+
+class dipdup.config.substrate_subscan.SubstrateSubscanDatasourceConfig(kind, url, api_key=None, http=None)
+Subscan datasource config
+
+- Parameters:
+
+kind (Literal['substrate.subscan']) – always ‘substrate.subscan’
+url (str) – API URL
+api_key (str | None) – API key
+http (HttpConfig | None) – HTTP client configuration
+
+
+
+
+
+
+
+
## dipdup.config.tzip_metadata.TzipMetadataDatasourceConfig
class dipdup.config.tzip_metadata.TzipMetadataDatasourceConfig(kind, network, url='https://metadata.dipdup.net', http=None)
@@ -1193,4 +1286,4 @@ description: "Config file reference"
-
\ 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)