From ba41ecdc276fb4696205aa6525c743b7ee3ace7f Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 13 Feb 2025 14:09:25 -0300 Subject: [PATCH 01/30] Universal config generator --- src/dipdup/_survey.py | 503 ------------------ src/dipdup/config/__init__.py | 9 +- src/dipdup/config/coinbase.py | 2 +- src/dipdup/config/evm.py | 2 +- src/dipdup/config/evm_etherscan.py | 2 +- src/dipdup/config/evm_events.py | 2 +- src/dipdup/config/evm_node.py | 2 +- src/dipdup/config/evm_subsquid.py | 2 +- src/dipdup/config/evm_transactions.py | 2 +- src/dipdup/config/http.py | 2 +- src/dipdup/config/ipfs.py | 2 +- src/dipdup/config/starknet.py | 2 +- src/dipdup/config/starknet_events.py | 2 +- src/dipdup/config/starknet_node.py | 2 +- src/dipdup/config/starknet_subsquid.py | 2 +- src/dipdup/config/substrate_events.py | 2 +- src/dipdup/config/substrate_node.py | 2 +- src/dipdup/config/substrate_subscan.py | 2 +- src/dipdup/config/substrate_subsquid.py | 2 +- src/dipdup/config/tezos.py | 2 +- src/dipdup/config/tezos_big_maps.py | 2 +- src/dipdup/config/tezos_events.py | 2 +- src/dipdup/config/tezos_head.py | 2 +- src/dipdup/config/tezos_operations.py | 4 +- src/dipdup/config/tezos_token_balances.py | 2 +- src/dipdup/config/tezos_token_transfers.py | 2 +- src/dipdup/config/tezos_tzkt.py | 2 +- src/dipdup/config/tzip_metadata.py | 2 +- src/dipdup/datasources/starknet_node.py | 8 +- src/dipdup/indexes/starknet_events/fetcher.py | 10 +- src/dipdup/project.py | 154 +++++- 31 files changed, 173 insertions(+), 565 deletions(-) delete mode 100644 src/dipdup/_survey.py diff --git a/src/dipdup/_survey.py b/src/dipdup/_survey.py deleted file mode 100644 index 5d88c16d6..000000000 --- a/src/dipdup/_survey.py +++ /dev/null @@ -1,503 +0,0 @@ -from typing import Any -from typing import TypedDict - -import survey # type: ignore[import-untyped] - -from dipdup.cli import echo -from dipdup.install import ask - - -class Pattern(TypedDict): - destination: str - entrypoint: str - - -class Handler(TypedDict): - name: str | None - callback: str | None - contract: str | None - path: str | None - tag: str | None - pattern: Pattern | None - to: str | None - method: str | None - - -class Datasource(TypedDict): - name: str - kind: str - url: str - ws_url: str | None - api_key: str | None - - -class Contract(TypedDict): - name: str - kind: str - address: str - typename: str - - -class Index(TypedDict): - name: str - kind: str - datasources: list[str] - handlers: list[Handler] | None - skip_history: str | None - first_level: int | None - last_level: int | None - callback: str | None - types: list[str] | None - contracts: list[str] | None - - -class DipDupSurveyConfig(TypedDict): - datasources: list[Datasource] - contracts: list[Contract] | None - indexes: list[Index] - - -class DatasourceConfig(TypedDict): - kind: str - requires_api_key: bool - default_url: str - name: str - - -class IndexerConfig(TypedDict): - handler_fields: list[str] - optional_fields: dict[str, str | int] - kind: str - - -class BlockchainConfig(TypedDict): - datasources: list[DatasourceConfig] - contract_kind: str - indexes: dict[str, IndexerConfig] - - -CONFIG_STRUCTURE: dict[str, BlockchainConfig] = { - 'evm': { - 'datasources': [ - { - 'kind': 'evm.subsquid', - 'requires_api_key': False, - 'default_url': 'https://v2.archive.subsquid.io/network/ethereum-mainnet', - 'name': 'subsquid', - }, - { - 'kind': 'evm.node', - 'requires_api_key': True, - 'default_url': 'https://eth-mainnet.g.alchemy.com/v2', - 'name': 'node', - }, - { - 'kind': 'evm.etherscan', - 'requires_api_key': True, - 'default_url': 'https://api.etherscan.io/api', - 'name': 'etherscan', - }, - ], - 'contract_kind': 'evm', - 'indexes': { - 'events': { - 'handler_fields': ['callback', 'contract', 'name'], - 'optional_fields': {}, - 'kind': 'evm.events', - }, - 'transactions': { - 'handler_fields': ['callback', 'to', 'method'], - 'optional_fields': { - 'first_level': 'integer', - }, - 'kind': 'evm.transactions', - }, - }, - }, - 'tezos': { - 'datasources': [ - { - 'kind': 'tezos.tzkt', - 'requires_api_key': False, - 'default_url': 'https://api.ghostnet.tzkt.io', - 'name': 'tzkt', - } - ], - 'contract_kind': 'tezos', - 'indexes': { - 'big_maps': { - 'handler_fields': ['callback', 'contract', 'path'], - 'optional_fields': { - 'skip_history': 'select', - }, - 'kind': 'tezos.big_maps', - }, - 'events': { - 'handler_fields': ['callback', 'contract', 'tag'], - 'optional_fields': {}, - 'kind': 'tezos.events', - }, - 'head': { - 'handler_fields': [], - 'optional_fields': { - 'callback': 'string', - }, - 'kind': 'tezos.head', - }, - 'operations': { - 'handler_fields': ['callback', 'pattern'], - 'optional_fields': {}, - 'kind': 'tezos.operations', - }, - 'operations_unfiltered': { - 'handler_fields': [], - 'optional_fields': { - 'types': 'select', - 'callback': 'string', - 'first_level': 'integer', - 'last_level': 'integer', - }, - 'kind': 'tezos.operations_unfiltered', - }, - 'token_balances': { - 'handler_fields': ['callback', 'contract'], - 'optional_fields': {}, - 'kind': 'tezos.token_balances', - }, - 'token_transfers': { - 'handler_fields': ['callback', 'contract'], - 'optional_fields': {}, - 'kind': 'tezos.token_transfers', - }, - }, - }, - 'starknet': { - 'datasources': [ - { - 'kind': 'starknet.subsquid', - 'requires_api_key': False, - 'default_url': 'https://v2.archive.subsquid.io/network/starknet-mainnet', - 'name': 'subsquid', - }, - { - 'kind': 'starknet.node', - 'requires_api_key': True, - 'default_url': 'https://starknet-mainnet.g.alchemy.com/v2', - 'name': 'node', - }, - ], - 'contract_kind': 'starknet', - 'indexes': { - 'events': { - 'handler_fields': ['callback', 'contract', 'name'], - 'optional_fields': {}, - 'kind': 'starknet.events', - } - }, - }, -} - - -def prompt_anyof( - question: str, - options: tuple[str, ...], - comments: tuple[str, ...], - default: int, -) -> tuple[int, str]: - """Ask user to choose one of the options; returns index and value""" - from tabulate import tabulate - - table = tabulate( - zip(options, comments, strict=False), - tablefmt='plain', - ) - index = survey.routines.select( - question + '\n', - options=table.split('\n'), - index=default, - ) - return index, options[index] - - -# Helper function to generate comments for datasource options -def get_datasource_comments(datasources: list[str]) -> tuple[str, ...]: - default_comments = { - 'evm.subsquid': 'Use Subsquid as your datasource for EVM.', - 'evm.node': 'Connect to an EVM node.', - 'evm.etherscan': 'Fetch ABI from Etherscan.', - 'tezos.tzkt': 'Use TzKT API for Tezos.', - 'starknet.subsquid': 'Use Subsquid for Starknet.', - 'starknet.node': 'Connect to a Starknet node.', - } - return tuple(default_comments.get(option, 'No description available') for option in datasources) - - -# Helper function to generate comments for indexes options -def get_index_comments(indexes: dict[str, IndexerConfig]) -> tuple[str, ...]: - default_comments: dict[str, str] = { - 'evm.events': 'Listen to EVM blockchain events.', - 'evm.transactions': 'Track EVM blockchain transactions.', - 'tezos.big_maps': 'Monitor changes in Tezos big maps.', - 'tezos.events': 'Track specific events in Tezos.', - 'tezos.head': 'Monitor Tezos chain head updates.', - 'tezos.operations': 'Track operations on Tezos blockchain.', - 'tezos.operations_unfiltered': 'Monitor unfiltered Tezos operations.', - 'tezos.token_balances': 'Track Tezos token balances.', - 'tezos.token_transfers': 'Monitor token transfers on Tezos.', - 'starknet.events': 'Listen to Starknet blockchain events.', - } - comments = [] - for _, config in indexes.items(): - kind = config.get('kind') - comment = default_comments.get(kind, 'No description available') # type: ignore[arg-type] - comments.append(comment) - return tuple(comments) - - -def query_handlers( - contract_names: list[str], - additional_fields: list[str] | None, -) -> list[Handler] | None: - - handlers: list[Handler] = [] - - if not additional_fields: - return None - - first = True - while first or ask('Add another handler?', False): - first = False - - handler: Handler = { - 'callback': '', - 'path': None, - 'tag': None, - 'pattern': None, - 'name': None, - 'contract': None, - 'to': None, - 'method': None, - } - - # Prompt for additional fields - for field in additional_fields: - if field in ['contract', 'to']: - handler[field] = prompt_anyof( # type: ignore[literal-required] - 'Choose contract for the handler', - tuple(contract_names), - ('Contract to listen to',) * len(contract_names), - 0, - )[1] - elif field == 'pattern': - handler['pattern'] = { - 'destination': prompt_anyof( - 'Choose contract for the handler', - tuple(contract_names), - ('Contract to listen to',) * len(contract_names), - 0, - )[1], - 'entrypoint': validate_non_empty_input( - survey.routines.input('Enter pattern entrypoint: ', value=''), 'entrypoint' - ), - } - else: - handler[field] = validate_non_empty_input( # type: ignore[literal-required] - survey.routines.input(f'Enter handler {field}: ', value=''), - field, - ) - - handlers.append(handler) - - return handlers - - -def query_optional_fields(optional_fields: dict[str, str | int]) -> dict[str, Any]: - field_values: dict[str, Any] = {} - for field, field_type in optional_fields.items(): - if field_type == 'select': - if field == 'types': - types: list[str] = [] - while True: - _, type = prompt_anyof( - 'Select operation type', - ('origination', 'transaction', 'migration'), - ('origination', 'transaction', 'migration'), - 0, - ) - types.append(type) - if not ask('Add another type?', False): - break - field_values[field] = types - else: - _, field_values[field] = prompt_anyof( - f'Select {field}', - ('never', 'always', 'auto'), - ('Never', 'Always', 'Auto'), - 0, - ) - else: - field_values[field] = survey.routines.input(f'Enter {field}: ') - - return field_values - - -def validate_non_empty_input(input_value: str, field_name: str) -> str: - while not input_value.strip(): - echo(f'{field_name} cannot be empty. Please enter a valid value.', fg='red') - input_value = survey.routines.input(f'Enter {field_name}: ') - return input_value - - -def filter_index_options( - indexes: tuple[str, ...], - index_comments: tuple[str, ...], - contracts: list[Contract], - blockchain: str, -) -> tuple[tuple[str, ...], tuple[str, ...]]: - blockchain_config = CONFIG_STRUCTURE[blockchain] - index_dict = blockchain_config['indexes'] - required_contract_fields = {'contract', 'to', 'pattern'} - if not contracts: - valid_indexes = [ - idx - for idx, index in enumerate(indexes) - if not required_contract_fields.intersection(set(index_dict[index].get('handler_fields', []))) or contracts - ] - - indexes = tuple(indexes[idx] for idx in valid_indexes) - index_comments = tuple(index_comments[idx] for idx in valid_indexes) - - return indexes, index_comments - - -def query_survey_config(blockchain: str) -> DipDupSurveyConfig: - blockchain_config = CONFIG_STRUCTURE[blockchain] - datasources: list[Datasource] = [] - contracts: list[Contract] = [] - indexes: list[Index] = [] - survey_config = DipDupSurveyConfig( - datasources=datasources, - contracts=contracts, - indexes=indexes, - ) - - first = True - while ask(f'Add {'' if first else 'another '}datasource?', first): - first = False - - datasource_options = blockchain_config['datasources'] - datasource_comments = get_datasource_comments([ds['kind'] for ds in datasource_options]) - - selected_index, _ = prompt_anyof( - f'Select the datasource kind for {blockchain}:', - tuple(ds['name'] for ds in datasource_options), - datasource_comments, - 0, - ) - selected_datasource = datasource_options[selected_index] - datasource_kind = selected_datasource['kind'] - - final_url = validate_non_empty_input( - survey.routines.input('Enter Datasource URL: ', value=selected_datasource.get('default_url', '')), - 'Datasource URL', - ) - api_key = None - ws_url = None - - if selected_datasource['requires_api_key']: - api_key = validate_non_empty_input( - survey.routines.input('Enter API key: ', value=''), - 'API key', - ) - if 'node' in datasource_kind: - final_url = '${NODE_URL:-' + final_url + '}/${NODE_API_KEY:-' + api_key + '}' - if datasource_kind == 'evm.node': - ws_url = survey.routines.input( - 'Enter WebSocket (wss) URL: ', value='wss://eth-mainnet.g.alchemy.com/v2' - ) - ws_url = '${NODE_WS_URL:-' + ws_url + '}/${NODE_API_KEY:-' + api_key + '}' - api_key = None - else: - api_key = '${ETHERSCAN_API_KEY:-' + api_key + '}' - - if datasource_kind != 'evm.etherscan': - api_key = None - - if 'subsquid' in datasource_kind: - final_url = '${SUBSQUID_URL:-' + final_url + '}' - - datasource: Datasource = { - 'kind': datasource_kind, - 'url': final_url, - 'ws_url': ws_url, - 'api_key': api_key, - 'name': selected_datasource['name'], - } - - datasources.append(datasource) - - first = True - while ask(f'Add {'' if first else 'another '}contract?', first): - first = False - - contract_name = validate_non_empty_input( - survey.routines.input('Enter contract name: '), - 'Contract name', - ) - contract_address = validate_non_empty_input( - survey.routines.input('Enter contract address: '), - 'Contract address', - ) - - contract: Contract = { - 'name': contract_name, - 'kind': blockchain_config['contract_kind'], - 'address': contract_address, - 'typename': contract_name, - } - contracts.append(contract) - - if not datasources: - return survey_config - - index_options = tuple(blockchain_config['indexes'].keys()) - index_comments = get_index_comments(blockchain_config['indexes']) - index_options, index_comments = filter_index_options(index_options, index_comments, contracts, blockchain) - - if not index_options: - return survey_config - - first = True - while ask(f'Add {'' if first else 'another '}index?', first): - first = False - - _, kind = prompt_anyof(f'Select the type of index for {blockchain}:', index_options, index_comments, 0) - - index_config = blockchain_config['indexes'][kind] - index_name = validate_non_empty_input( - survey.routines.input('Enter the index name: '), - 'Indexer name', - ) - - index: Index = { - 'name': index_name, - 'kind': index_config['kind'], - 'datasources': [ds['name'] for ds in datasources], - 'handlers': None, - 'skip_history': None, - 'first_level': None, - 'last_level': None, - 'callback': None, - 'types': None, - 'contracts': None, - } - - handlers = query_handlers([c['name'] for c in contracts], index_config.get('handler_fields')) - index['handlers'] = handlers - - if 'optional_fields' in index_config: - index.update(query_optional_fields(index_config['optional_fields'])) # type: ignore[typeddict-item] - - indexes.append(index) - - return survey_config diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index a0cb103c9..30aa1234a 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -100,7 +100,7 @@ class SqliteDatabaseConfig: :param immune_tables: List of tables to preserve during reindexing """ - kind: Literal['sqlite'] + kind: Literal['sqlite'] = 'sqlite' path: str = DEFAULT_SQLITE_PATH immune_tables: set[str] = Field(default_factory=set) @@ -139,7 +139,7 @@ class PostgresDatabaseConfig: :param connection_timeout: Connection timeout """ - kind: Literal['postgres'] + kind: Literal['postgres'] = 'postgres' host: str user: str = DEFAULT_POSTGRES_USER database: str = DEFAULT_POSTGRES_DATABASE @@ -586,9 +586,7 @@ class DipDupConfig: spec_version: ToStr package: str datasources: dict[str, DatasourceConfigU] = Field(default_factory=dict) - database: SqliteDatabaseConfig | PostgresDatabaseConfig = Field( - default_factory=lambda *a, **kw: SqliteDatabaseConfig(kind='sqlite') - ) + database: DatabaseConfigU = 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) @@ -1162,6 +1160,7 @@ def _set_names(self) -> None: from dipdup.config.tzip_metadata import TzipMetadataDatasourceConfig # NOTE: Unions for Pydantic config deserialization +DatabaseConfigU = SqliteDatabaseConfig | PostgresDatabaseConfig RuntimeConfigU = SubstrateRuntimeConfig ContractConfigU = EvmContractConfig | TezosContractConfig | StarknetContractConfig DatasourceConfigU = ( diff --git a/src/dipdup/config/coinbase.py b/src/dipdup/config/coinbase.py index fe194af3f..41e3d0cfe 100644 --- a/src/dipdup/config/coinbase.py +++ b/src/dipdup/config/coinbase.py @@ -21,7 +21,7 @@ class CoinbaseDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['coinbase'] + kind: Literal['coinbase'] = 'coinbase' api_key: str | None = None secret_key: str | None = Field(default=None, repr=False) passphrase: str | None = Field(default=None, repr=False) diff --git a/src/dipdup/config/evm.py b/src/dipdup/config/evm.py index d903937a1..9b0e4714b 100644 --- a/src/dipdup/config/evm.py +++ b/src/dipdup/config/evm.py @@ -55,7 +55,7 @@ class EvmContractConfig(ContractConfig): :param typename: Alias for the contract script """ - kind: Literal['evm'] + kind: Literal['evm'] = 'evm' address: EvmAddress | None = None abi: EvmAddress | None = None typename: str | None = None diff --git a/src/dipdup/config/evm_etherscan.py b/src/dipdup/config/evm_etherscan.py index 2ff8ba722..8e24df9d9 100644 --- a/src/dipdup/config/evm_etherscan.py +++ b/src/dipdup/config/evm_etherscan.py @@ -23,7 +23,7 @@ class EvmEtherscanDatasourceConfig(DatasourceConfig): """ # NOTE: Alias, remove in 9.0 - kind: Literal['evm.etherscan'] | Literal['abi.etherscan'] + kind: Literal['evm.etherscan'] | Literal['abi.etherscan'] = 'evm.etherscan' url: str api_key: str | None = None diff --git a/src/dipdup/config/evm_events.py b/src/dipdup/config/evm_events.py index aedb92fe3..f277227c1 100644 --- a/src/dipdup/config/evm_events.py +++ b/src/dipdup/config/evm_events.py @@ -61,7 +61,7 @@ class EvmEventsIndexConfig(EvmIndexConfig): :param last_level: Level to stop indexing and disable this index """ - kind: Literal['evm.events'] + kind: Literal['evm.events'] = 'evm.events' datasources: tuple[Alias[EvmDatasourceConfigU], ...] handlers: tuple[EvmEventsHandlerConfig, ...] diff --git a/src/dipdup/config/evm_node.py b/src/dipdup/config/evm_node.py index 9ee1eb170..fe7a0ec95 100644 --- a/src/dipdup/config/evm_node.py +++ b/src/dipdup/config/evm_node.py @@ -22,7 +22,7 @@ class EvmNodeDatasourceConfig(DatasourceConfig): :param rollback_depth: A number of blocks to store in database for rollback """ - kind: Literal['evm.node'] + kind: Literal['evm.node'] = 'evm.node' url: Url ws_url: WsUrl | None = None http: HttpConfig | None = None diff --git a/src/dipdup/config/evm_subsquid.py b/src/dipdup/config/evm_subsquid.py index 9ce1ac9a4..c8848480e 100644 --- a/src/dipdup/config/evm_subsquid.py +++ b/src/dipdup/config/evm_subsquid.py @@ -19,7 +19,7 @@ class EvmSubsquidDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['evm.subsquid'] + kind: Literal['evm.subsquid'] = 'evm.subsquid' url: Url http: HttpConfig | None = None diff --git a/src/dipdup/config/evm_transactions.py b/src/dipdup/config/evm_transactions.py index 956685206..3fdeb06c7 100644 --- a/src/dipdup/config/evm_transactions.py +++ b/src/dipdup/config/evm_transactions.py @@ -93,7 +93,7 @@ class EvmTransactionsIndexConfig(EvmIndexConfig): :param last_level: Level to stop indexing at """ - kind: Literal['evm.transactions'] + kind: Literal['evm.transactions'] = 'evm.transactions' datasources: tuple[Alias[EvmDatasourceConfigU], ...] handlers: tuple[EvmTransactionsHandlerConfig, ...] diff --git a/src/dipdup/config/http.py b/src/dipdup/config/http.py index 6b3cfcf0c..b4341e90e 100644 --- a/src/dipdup/config/http.py +++ b/src/dipdup/config/http.py @@ -18,6 +18,6 @@ class HttpDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['http'] + kind: Literal['http'] = 'http' url: str http: HttpConfig | None = None diff --git a/src/dipdup/config/ipfs.py b/src/dipdup/config/ipfs.py index 148ce6df1..90993b647 100644 --- a/src/dipdup/config/ipfs.py +++ b/src/dipdup/config/ipfs.py @@ -20,6 +20,6 @@ class IpfsDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['ipfs'] + kind: Literal['ipfs'] = 'ipfs' url: str = DEFAULT_IPFS_URL http: HttpConfig | None = None diff --git a/src/dipdup/config/starknet.py b/src/dipdup/config/starknet.py index 52469d5a9..8e026c70d 100644 --- a/src/dipdup/config/starknet.py +++ b/src/dipdup/config/starknet.py @@ -65,7 +65,7 @@ class StarknetContractConfig(ContractConfig): :param typename: Alias for the contract script """ - kind: Literal['starknet'] + kind: Literal['starknet'] = 'starknet' address: StarknetAddress | None = None abi: StarknetAddress | None = None typename: str | None = None diff --git a/src/dipdup/config/starknet_events.py b/src/dipdup/config/starknet_events.py index e09b69392..14e7cf901 100644 --- a/src/dipdup/config/starknet_events.py +++ b/src/dipdup/config/starknet_events.py @@ -61,7 +61,7 @@ class StarknetEventsIndexConfig(StarknetIndexConfig): """ - kind: Literal['starknet.events'] + kind: Literal['starknet.events'] = 'starknet.events' datasources: tuple[Alias[StarknetDatasourceConfigU], ...] handlers: tuple[StarknetEventsHandlerConfig, ...] diff --git a/src/dipdup/config/starknet_node.py b/src/dipdup/config/starknet_node.py index 5e2b72251..54318a36a 100644 --- a/src/dipdup/config/starknet_node.py +++ b/src/dipdup/config/starknet_node.py @@ -22,7 +22,7 @@ class StarknetNodeDatasourceConfig(DatasourceConfig): :param rollback_depth: A number of blocks to store in database for rollback """ - kind: Literal['starknet.node'] + kind: Literal['starknet.node'] = 'starknet.node' url: Url ws_url: WsUrl | None = None http: HttpConfig | None = None diff --git a/src/dipdup/config/starknet_subsquid.py b/src/dipdup/config/starknet_subsquid.py index 581f8400d..6b270ec7a 100644 --- a/src/dipdup/config/starknet_subsquid.py +++ b/src/dipdup/config/starknet_subsquid.py @@ -19,7 +19,7 @@ class StarknetSubsquidDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['starknet.subsquid'] + kind: Literal['starknet.subsquid'] = 'starknet.subsquid' url: Url http: HttpConfig | None = None diff --git a/src/dipdup/config/substrate_events.py b/src/dipdup/config/substrate_events.py index c814fe712..5e008b656 100644 --- a/src/dipdup/config/substrate_events.py +++ b/src/dipdup/config/substrate_events.py @@ -62,7 +62,7 @@ class SubstrateEventsIndexConfig(SubstrateIndexConfig): :param runtime: Substrate runtime """ - kind: Literal['substrate.events'] + kind: Literal['substrate.events'] = 'substrate.events' datasources: tuple[Alias[SubstrateDatasourceConfigU], ...] handlers: tuple[SubstrateEventsHandlerConfig, ...] runtime: Alias[SubstrateRuntimeConfig] diff --git a/src/dipdup/config/substrate_node.py b/src/dipdup/config/substrate_node.py index a7eeb402c..a0bd09f4f 100644 --- a/src/dipdup/config/substrate_node.py +++ b/src/dipdup/config/substrate_node.py @@ -21,7 +21,7 @@ class SubstrateNodeDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['substrate.node'] + kind: Literal['substrate.node'] = 'substrate.node' url: Url ws_url: WsUrl | None = None http: HttpConfig | None = None diff --git a/src/dipdup/config/substrate_subscan.py b/src/dipdup/config/substrate_subscan.py index 30dc2d178..8167da3b4 100644 --- a/src/dipdup/config/substrate_subscan.py +++ b/src/dipdup/config/substrate_subscan.py @@ -19,7 +19,7 @@ class SubstrateSubscanDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['substrate.subscan'] + kind: Literal['substrate.subscan'] = 'substrate.subscan' url: str api_key: str | None = None diff --git a/src/dipdup/config/substrate_subsquid.py b/src/dipdup/config/substrate_subsquid.py index b2de5eb3e..777ae1880 100644 --- a/src/dipdup/config/substrate_subsquid.py +++ b/src/dipdup/config/substrate_subsquid.py @@ -19,7 +19,7 @@ class SubstrateSubsquidDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['substrate.subsquid'] + kind: Literal['substrate.subsquid'] = 'substrate.subsquid' url: Url http: HttpConfig | None = None diff --git a/src/dipdup/config/tezos.py b/src/dipdup/config/tezos.py index 707cfe5a5..83e3490cf 100644 --- a/src/dipdup/config/tezos.py +++ b/src/dipdup/config/tezos.py @@ -59,7 +59,7 @@ class TezosContractConfig(ContractConfig): :param typename: Alias for the contract script """ - kind: Literal['tezos'] + kind: Literal['tezos'] = 'tezos' address: TezosAddress | None = None code_hash: int | TezosAddress | None = None typename: str | None = None diff --git a/src/dipdup/config/tezos_big_maps.py b/src/dipdup/config/tezos_big_maps.py index 0777a1ea3..d09e664d6 100644 --- a/src/dipdup/config/tezos_big_maps.py +++ b/src/dipdup/config/tezos_big_maps.py @@ -79,7 +79,7 @@ class TezosBigMapsIndexConfig(TezosIndexConfig): :param last_level: Level to stop indexing at """ - kind: Literal['tezos.big_maps'] + kind: Literal['tezos.big_maps'] = 'tezos.big_maps' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] handlers: tuple[TezosBigMapsHandlerConfig, ...] diff --git a/src/dipdup/config/tezos_events.py b/src/dipdup/config/tezos_events.py index 1c39d4d3d..a38bb394c 100644 --- a/src/dipdup/config/tezos_events.py +++ b/src/dipdup/config/tezos_events.py @@ -83,7 +83,7 @@ class TezosEventsIndexConfig(TezosIndexConfig): :param last_level: Last block level to index """ - kind: Literal['tezos.events'] + kind: Literal['tezos.events'] = 'tezos.events' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] handlers: tuple[TezosEventsHandlerConfigU, ...] diff --git a/src/dipdup/config/tezos_head.py b/src/dipdup/config/tezos_head.py index f727a2d2d..c76b57ecc 100644 --- a/src/dipdup/config/tezos_head.py +++ b/src/dipdup/config/tezos_head.py @@ -41,7 +41,7 @@ class TezosHeadIndexConfig(TezosIndexConfig): :param datasources: `tezos` datasources to use """ - kind: Literal['tezos.head'] + kind: Literal['tezos.head'] = 'tezos.head' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] callback: str diff --git a/src/dipdup/config/tezos_operations.py b/src/dipdup/config/tezos_operations.py index 702282d28..e6fa68bfe 100644 --- a/src/dipdup/config/tezos_operations.py +++ b/src/dipdup/config/tezos_operations.py @@ -306,7 +306,7 @@ class TezosOperationsIndexConfig(TezosIndexConfig): :param last_level: Level to stop indexing at """ - kind: Literal['tezos.operations'] + kind: Literal['tezos.operations'] = 'tezos.operations' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] handlers: tuple[TezosOperationsHandlerConfig, ...] contracts: list[Alias[TezosContractConfig]] = Field(default_factory=list) @@ -431,7 +431,7 @@ class TezosOperationsUnfilteredIndexConfig(TezosIndexConfig): :param last_level: Level to stop indexing at """ - kind: Literal['tezos.operations_unfiltered'] + kind: Literal['tezos.operations_unfiltered'] = 'tezos.operations_unfiltered' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] callback: str types: tuple[TezosOperationType, ...] = (TezosOperationType.transaction,) diff --git a/src/dipdup/config/tezos_token_balances.py b/src/dipdup/config/tezos_token_balances.py index 3dbdbe0f4..ecd7667ed 100644 --- a/src/dipdup/config/tezos_token_balances.py +++ b/src/dipdup/config/tezos_token_balances.py @@ -56,7 +56,7 @@ class TezosTokenBalancesIndexConfig(TezosIndexConfig): :param last_level: Level to stop indexing at """ - kind: Literal['tezos.token_balances'] + kind: Literal['tezos.token_balances'] = 'tezos.token_balances' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] handlers: tuple[TezosTokenBalancesHandlerConfig, ...] diff --git a/src/dipdup/config/tezos_token_transfers.py b/src/dipdup/config/tezos_token_transfers.py index bd76a29b7..caf00e227 100644 --- a/src/dipdup/config/tezos_token_transfers.py +++ b/src/dipdup/config/tezos_token_transfers.py @@ -59,7 +59,7 @@ class TezosTokenTransfersIndexConfig(TezosIndexConfig): :param last_level: Level to stop indexing at """ - kind: Literal['tezos.token_transfers'] + kind: Literal['tezos.token_transfers'] = 'tezos.token_transfers' datasources: tuple[Alias[TezosTzktDatasourceConfig], ...] handlers: tuple[TezosTokenTransfersHandlerConfig, ...] diff --git a/src/dipdup/config/tezos_tzkt.py b/src/dipdup/config/tezos_tzkt.py index 9eba47d18..564fe414a 100644 --- a/src/dipdup/config/tezos_tzkt.py +++ b/src/dipdup/config/tezos_tzkt.py @@ -34,7 +34,7 @@ class TezosTzktDatasourceConfig(DatasourceConfig): :param rollback_depth: Number of blocks to keep in the database to handle reorgs """ - kind: Literal['tezos.tzkt'] + kind: Literal['tezos.tzkt'] = 'tezos.tzkt' url: Url = DEFAULT_TZKT_URL http: HttpConfig | None = None buffer_size: int = 0 diff --git a/src/dipdup/config/tzip_metadata.py b/src/dipdup/config/tzip_metadata.py index 1a26ab5e7..b787fde43 100644 --- a/src/dipdup/config/tzip_metadata.py +++ b/src/dipdup/config/tzip_metadata.py @@ -22,7 +22,7 @@ class TzipMetadataDatasourceConfig(DatasourceConfig): :param http: HTTP client configuration """ - kind: Literal['tzip_metadata'] + kind: Literal['tzip_metadata'] = 'tzip_metadata' network: TzipMetadataNetwork url: str = DEFAULT_TZIP_METADATA_URL http: HttpConfig | None = None diff --git a/src/dipdup/datasources/starknet_node.py b/src/dipdup/datasources/starknet_node.py index f05d53108..d210ea430 100644 --- a/src/dipdup/datasources/starknet_node.py +++ b/src/dipdup/datasources/starknet_node.py @@ -31,7 +31,9 @@ class StarknetNodeDatasource(IndexDatasource[StarknetNodeDatasourceConfig]): def __init__(self, config: StarknetNodeDatasourceConfig, merge_subscriptions: bool = False) -> None: super().__init__(config, merge_subscriptions) self._starknetpy: StarknetpyClient | None = None - self._block_cache: LRU[int, StarknetBlockWithTxHashes | PendingStarknetBlockWithTxHashes] = LRU(BLOCK_CACHE_SIZE) + self._block_cache: LRU[int, StarknetBlockWithTxHashes | PendingStarknetBlockWithTxHashes] = LRU( + BLOCK_CACHE_SIZE + ) @property def starknetpy(self) -> 'StarknetpyClient': @@ -82,7 +84,9 @@ async def get_events( continuation_token=continuation_token, ) - async def get_block_with_tx_hashes(self, block_hash: int) -> Union['StarknetBlockWithTxHashes', 'PendingStarknetBlockWithTxHashes']: + async def get_block_with_tx_hashes( + self, block_hash: int + ) -> Union['StarknetBlockWithTxHashes', 'PendingStarknetBlockWithTxHashes']: if block := self._block_cache.get(block_hash, None): return block diff --git a/src/dipdup/indexes/starknet_events/fetcher.py b/src/dipdup/indexes/starknet_events/fetcher.py index ddc7e432b..d23e8893b 100644 --- a/src/dipdup/indexes/starknet_events/fetcher.py +++ b/src/dipdup/indexes/starknet_events/fetcher.py @@ -55,7 +55,7 @@ class EventFetcherChannel(FetcherChannel[StarknetEventData, StarknetNodeDatasour async def fetch(self) -> None: address, key0s = next(iter(self._filter)) - + datasource = self._datasources[0] events_chunk = await datasource.get_events( @@ -67,9 +67,9 @@ async def fetch(self) -> None: ) for event in events_chunk.events: - # NOTE: + # NOTE: if event.block_hash is None or event.transaction_hash is None: - _logger.info("Skipping event. No block_hash or transaction_hash found in %s", event) + _logger.info('Skipping event. No block_hash or transaction_hash found in %s', event) continue block = await datasource.get_block_with_tx_hashes( @@ -77,7 +77,7 @@ async def fetch(self) -> None: ) if block is None: - _logger.info("Skipping event. No block exists for block_hash. BlackHash=%s", event.block_hash) + _logger.info('Skipping event. No block exists for block_hash. BlackHash=%s', event.block_hash) continue timestamp = block.timestamp @@ -85,7 +85,7 @@ async def fetch(self) -> None: # NOTE: This event is corrupt, possibly due to old age. if transaction_idx < 0: - _logger.info("Skipping event. No transaction_hash exists in block. TxHash=%s", event.transaction_hash) + _logger.info('Skipping event. No transaction_hash exists in block. TxHash=%s', event.transaction_hash) continue self._buffer[event.block_number].append( # type: ignore[index] diff --git a/src/dipdup/project.py b/src/dipdup/project.py index 5b983f668..91b68e9d3 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -3,21 +3,26 @@ Ask user some question with Click; render Jinja2 templates with answers. """ +import dataclasses import logging import re +from itertools import chain from pathlib import Path +from typing import Any +from typing import get_args from pydantic import ConfigDict from pydantic import TypeAdapter from pydantic.dataclasses import dataclass +from pydantic.fields import FieldInfo from typing_extensions import TypedDict from dipdup import __version__ -from dipdup._survey import DipDupSurveyConfig -from dipdup._survey import prompt_anyof -from dipdup._survey import query_survey_config from dipdup.cli import big_yellow_echo from dipdup.cli import echo +from dipdup.config import DatabaseConfigU +from dipdup.config import DatasourceConfigU +from dipdup.config import RuntimeConfigU from dipdup.config import ToStr from dipdup.env import get_package_path from dipdup.env import get_pyproject_name @@ -80,7 +85,7 @@ class Answers(TypedDict): hasura_image: str line_length: ToStr package_manager: str - _survey_config: DipDupSurveyConfig | None + _survey_config: dict[str, Any] | None def get_default_answers() -> Answers: @@ -125,21 +130,22 @@ def get_replay_path(name: str) -> Path: return Path(__file__).parent / 'projects' / name / 'replay.yaml' -def template_from_terminal() -> tuple[str | None, DipDupSurveyConfig | None]: - _, mode = prompt_anyof( - question='How would you like to set up your new DipDup project?', - options=( - 'From template', - 'Interactively', - 'Blank', - ), - comments=( - 'Use one of demo projects', - 'Guided setup with prompts', - 'Begin with an empty project', - ), - default=0, - ) +def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: + # _, mode = prompt_anyof( + # question='How would you like to set up your new DipDup project?', + # options=( + # 'From template', + # 'Interactively', + # 'Blank', + # ), + # comments=( + # 'Use one of demo projects', + # 'Guided setup with prompts', + # 'Begin with an empty project', + # ), + # default=0, + # ) + mode = 'Interactively' if mode == 'Blank': return ('demo_blank', None) @@ -151,25 +157,28 @@ def template_from_terminal() -> tuple[str | None, DipDupSurveyConfig | None]: 'Starknet', 'Substrate', 'Tezos', + '[multiple]', ), comments=( - 'EVM-compatible blockchains', + 'EVM-compatible', 'Starknet', 'Substrate', 'Tezos', + 'Disable filtering', ), default=0, ) - blockchain = res[1].lower() + namespace = res[1].lower() if res[1] != '[multiple]' else None if mode == 'Interactively': replay_path = get_replay_path('demo_blank') - survey_config = query_survey_config(blockchain) + survey_config = query_survey_config(namespace) return ('demo_blank', survey_config) if mode == 'From template': options, comments = [], [] - for name in TEMPLATES[blockchain]: + templates = TEMPLATES[namespace] if namespace else chain(v for v in TEMPLATES.values()) + for name in templates: replay_path = get_replay_path(name) _answers = answers_from_replay(replay_path) options.append(_answers['template']) @@ -409,3 +418,102 @@ def _render(answers: Answers, template_path: Path, output_path: Path, force: boo ) write(output_path, content, overwrite=force) + + +def fill_config_from_input( + config: dict[str, Any], + section: str, + section_kinds: tuple[type], + filter: str, +) -> None: + section_dict = config.get(section, {}) + another = False + + print(f'Configuring `{section}` section') + + while True: + add_entity = input(f'Do you want to add{' another' if another else ''} one? (yes/no): ').strip().lower() + if add_entity != 'yes': + break + + # Ask for the entity name + if section == 'database': + name = '' + else: + name = input('Enter a name for the entity: ').strip() + + # Ask for the type of the entity + print('Available entity types:') + for i, entity_type in enumerate(section_kinds, start=1): + kind = entity_type.__dataclass_fields__['kind'].default + assert kind + + if filter and '.' in kind and not kind.startswith(filter): + continue + + print(f'{i}. {kind}') + + type_choice = int(input('Choose an entity type (number): ').strip()) - 1 + # entity_type_name = list(section_kinds)[type_choice] + entity_model = section_kinds[type_choice] + + # Gather input for the fields of the chosen type + entity_data = {} + for field_name, field in entity_model.__dataclass_fields__.items(): + if field_name in {'kind', 'http'}: + continue + + default = field.default + if default is dataclasses.MISSING: + default = None + elif isinstance(default, FieldInfo): + # else: + # print(default.__dict__) + default = default.default_factory() + + field_value = input(f'Enter value for `{field_name}` [{field.type}] ({default}): ').strip() + entity_data[field_name] = field_value or default + + # Validate and add the entity + try: + entity_instance = entity_model(**entity_data) + section_dict[name] = entity_instance.__dict__ + another = True + except Exception as e: + print(f'Error: {e}. Please try again.') + + +def prompt_anyof( + question: str, + options: tuple[str, ...], + comments: tuple[str, ...], + default: int, +) -> tuple[int, str]: + """Ask user to choose one of the options; returns index and value""" + import survey + from tabulate import tabulate + + table = tabulate( + zip(options, comments, strict=False), + tablefmt='plain', + ) + index = survey.routines.select( + question + '\n', + options=table.split('\n'), + index=default, + ) + return index, options[index] + + +def query_survey_config(namespace: str | None) -> dict[str, Any]: + config_dict = {} + + # NOTE: It's not a config section; we'll handle it later + fill_config_from_input(config_dict, 'database', get_args(DatabaseConfigU), namespace) + + if namespace == 'substrate': + fill_config_from_input(config_dict, 'runtimes', get_args(RuntimeConfigU), namespace) + + fill_config_from_input(config_dict, 'datasources', get_args(DatasourceConfigU), namespace) + + return config_dict From e38a71e6bd66818f549ab40e9b74239318c26bd4 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 13 Feb 2025 19:58:22 -0300 Subject: [PATCH 02/30] almost there --- src/dipdup/cli.py | 21 +++++- src/dipdup/config/__init__.py | 2 +- src/dipdup/project.py | 116 ++++++++++++++++++++-------------- src/dipdup/yaml.py | 2 +- 4 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 6eccc226a..3b8bd4761 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -27,6 +27,7 @@ from dipdup.install import EPILOG from dipdup.install import WELCOME_ASCII from dipdup.sys import set_up_process +from dipdup.yaml import DipDupYAMLConfig if TYPE_CHECKING: from dipdup.config import DipDupConfig @@ -849,6 +850,7 @@ async def new( from dipdup.project import answers_from_terminal from dipdup.project import get_default_answers from dipdup.project import render_project + from dipdup.project import template_from_terminal if quiet: answers = get_default_answers() @@ -860,13 +862,30 @@ async def new( answers['template'] = template else: try: - answers = answers_from_terminal(template) + answers = answers_from_terminal() + answers['template'] = template or 'demo_blank' + + if template: + echo(f'Using template `{template}`\n') + config_dict = {} + else: + template, config_dict = template_from_terminal() + except Escape: return _logger.info('Rendering project') render_project(answers, force) + if config_dict: + config_dict['package'] = answers['package'] + config_dict['spec_version'] = '3.0' + + config = DipDupYAMLConfig(**config_dict) + + path = env.get_package_path(config['package']) / ROOT_CONFIG + path.write_text(config.dump()) + _logger.info('Initializing project') config = DipDupConfig.load([Path(answers['package'])]) config.initialize() diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 30aa1234a..0020e5b8c 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -316,7 +316,7 @@ class IndexTemplateConfig(NameMixin): """ - kind = 'template' + kind: Literal['template'] = 'template' template: str values: dict[str, Any] first_level: int = 0 diff --git a/src/dipdup/project.py b/src/dipdup/project.py index 91b68e9d3..b97342268 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -20,8 +20,10 @@ from dipdup import __version__ from dipdup.cli import big_yellow_echo from dipdup.cli import echo -from dipdup.config import DatabaseConfigU from dipdup.config import DatasourceConfigU +from dipdup.config import HookConfig +from dipdup.config import IndexConfigU +from dipdup.config import JobConfig from dipdup.config import RuntimeConfigU from dipdup.config import ToStr from dipdup.env import get_package_path @@ -34,6 +36,12 @@ CODEGEN_HEADER = f'# generated by DipDup {__version__.split("+")[0]}' +SINGULAR_FORMS = { + 'datasources': 'datasource', + 'indexes': 'index', + 'hooks': 'hook', + 'jobs': 'job', +} # NOTE: All templates are stored in src/dipdup/projects TEMPLATES: dict[str, tuple[str, ...]] = { @@ -85,7 +93,6 @@ class Answers(TypedDict): hasura_image: str line_length: ToStr package_manager: str - _survey_config: dict[str, Any] | None def get_default_answers() -> Answers: @@ -103,7 +110,6 @@ def get_default_answers() -> Answers: hasura_image='hasura/graphql-engine:latest', line_length='120', package_manager='uv', - _survey_config=None, ) @@ -131,21 +137,20 @@ def get_replay_path(name: str) -> Path: def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: - # _, mode = prompt_anyof( - # question='How would you like to set up your new DipDup project?', - # options=( - # 'From template', - # 'Interactively', - # 'Blank', - # ), - # comments=( - # 'Use one of demo projects', - # 'Guided setup with prompts', - # 'Begin with an empty project', - # ), - # default=0, - # ) - mode = 'Interactively' + _, mode = prompt_anyof( + question='How would you like to set up your new DipDup project?', + options=( + 'Interactively', + 'From template', + 'Blank', + ), + comments=( + 'Guided setup with prompts', + 'Use one of demo projects', + 'Begin with an empty project', + ), + default=0, + ) if mode == 'Blank': return ('demo_blank', None) @@ -164,7 +169,7 @@ def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: 'Starknet', 'Substrate', 'Tezos', - 'Disable filtering', + '(disable filtering)', ), default=0, ) @@ -172,8 +177,8 @@ def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: if mode == 'Interactively': replay_path = get_replay_path('demo_blank') - survey_config = query_survey_config(namespace) - return ('demo_blank', survey_config) + config_dict = config_from_terminal(namespace) + return ('demo_blank', config_dict) if mode == 'From template': options, comments = [], [] @@ -195,7 +200,7 @@ def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: raise NotImplementedError -def answers_from_terminal(template: str | None) -> Answers: +def answers_from_terminal() -> Answers: """Script running on dipdup new command and will create a new project base from interactive survey""" import survey # type: ignore[import-untyped] @@ -206,14 +211,6 @@ def answers_from_terminal(template: str | None) -> Answers: answers = get_default_answers() - if template: - echo(f'Using template `{template}`\n') - else: - template, survey_config = template_from_terminal() - answers['_survey_config'] = survey_config - - answers['template'] = template or 'demo_blank' - big_yellow_echo('Set up project') while True: @@ -301,6 +298,7 @@ def answers_from_terminal(template: str | None) -> Answers: 'Enter maximum line length for linters: ', value=answers['line_length'], ) + return answers @@ -424,28 +422,31 @@ def fill_config_from_input( config: dict[str, Any], section: str, section_kinds: tuple[type], - filter: str, + filter: str | None, ) -> None: section_dict = config.get(section, {}) another = False - print(f'Configuring `{section}` section') + print(f'Configuring `{section}` section \n') while True: - add_entity = input(f'Do you want to add{' another' if another else ''} one? (yes/no): ').strip().lower() + entity_singular = SINGULAR_FORMS[section] + another_str = 'another' if another else 'the first' + add_entity = input(f'Do you want to add {another_str} {entity_singular}? (yes/no): ').strip().lower() if add_entity != 'yes': break # Ask for the entity name - if section == 'database': - name = '' - else: - name = input('Enter a name for the entity: ').strip() + name = input(f'Enter {entity_singular} name: ').strip() # Ask for the type of the entity - print('Available entity types:') + print(f'Available {entity_singular} kinds:') for i, entity_type in enumerate(section_kinds, start=1): - kind = entity_type.__dataclass_fields__['kind'].default + try: + kind = entity_type.__dataclass_fields__['kind'].default + except KeyError: + kind = entity_type.__name__ + assert kind if filter and '.' in kind and not kind.startswith(filter): @@ -453,8 +454,11 @@ def fill_config_from_input( print(f'{i}. {kind}') - type_choice = int(input('Choose an entity type (number): ').strip()) - 1 - # entity_type_name = list(section_kinds)[type_choice] + if i == 1: + type_choice = 0 + else: + type_choice = int(input(f'Choose {entity_singular} type (number): ').strip()) - 1 + entity_model = section_kinds[type_choice] # Gather input for the fields of the chosen type @@ -462,9 +466,14 @@ def fill_config_from_input( for field_name, field in entity_model.__dataclass_fields__.items(): if field_name in {'kind', 'http'}: continue + if field_name.startswith('_'): + continue + if field_name == 'handlers': + entity_data[field_name] = () + continue default = field.default - if default is dataclasses.MISSING: + if default == dataclasses.MISSING: default = None elif isinstance(default, FieldInfo): # else: @@ -472,6 +481,14 @@ def fill_config_from_input( default = default.default_factory() field_value = input(f'Enter value for `{field_name}` [{field.type}] ({default}): ').strip() + if field_value == '': + field_value = default + + # NOTE: Basic transformations: string lists + if isinstance(field_value, str): + if ',' in field_value: + field_value = field_value.split(',') + entity_data[field_name] = field_value or default # Validate and add the entity @@ -482,6 +499,10 @@ def fill_config_from_input( except Exception as e: print(f'Error: {e}. Please try again.') + print(section_dict) + + config[section] = section_dict + def prompt_anyof( question: str, @@ -505,15 +526,16 @@ def prompt_anyof( return index, options[index] -def query_survey_config(namespace: str | None) -> dict[str, Any]: +def config_from_terminal(namespace: str | None) -> dict[str, Any]: config_dict = {} - # NOTE: It's not a config section; we'll handle it later - fill_config_from_input(config_dict, 'database', get_args(DatabaseConfigU), namespace) - - if namespace == 'substrate': + # NOTE: Substrate or multichain + if namespace in {'substrate', None}: fill_config_from_input(config_dict, 'runtimes', get_args(RuntimeConfigU), namespace) fill_config_from_input(config_dict, 'datasources', get_args(DatasourceConfigU), namespace) + fill_config_from_input(config_dict, 'indexes', get_args(IndexConfigU), namespace) + fill_config_from_input(config_dict, 'hooks', (HookConfig,), None) + fill_config_from_input(config_dict, 'jobs', (JobConfig,), None) return config_dict diff --git a/src/dipdup/yaml.py b/src/dipdup/yaml.py index 771c20747..3f30fb710 100644 --- a/src/dipdup/yaml.py +++ b/src/dipdup/yaml.py @@ -45,7 +45,7 @@ def exclude_none(config_json: Any) -> Any: if isinstance(config_json, list | tuple): return [exclude_none(i) for i in config_json if i is not None] if isinstance(config_json, dict): - return {k: exclude_none(v) for k, v in config_json.items() if v is not None} + return {k: exclude_none(v) for k, v in config_json.items() if v is not None and not k.startswith('_')} return config_json From 35efc9c5b7111792dda8a995d2de610bc77992a2 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sat, 15 Feb 2025 15:12:24 -0300 Subject: [PATCH 03/30] another iteration --- CHANGELOG.md | 10 +++++ src/dipdup/cli.py | 8 +++- src/dipdup/config/coinbase.py | 4 ++ src/dipdup/datasources/coinbase.py | 1 - src/dipdup/project.py | 63 +++++++++++++++++++++++------- 5 files changed, 69 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4b01189..2c9973223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ 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] + +### Added + +- cli: Rewritten interactive mode for `new` command. + +### Fixed + +- coinbase: Fixed crash when using coinbase datasource. + ## [8.2.0] - 2025-02-10 ### Added diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 3b8bd4761..c1459b81f 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -878,8 +878,12 @@ async def new( render_project(answers, force) if config_dict: - config_dict['package'] = answers['package'] - config_dict['spec_version'] = '3.0' + # NOTE: Preserve the header at the top of the file + config_dict = { + 'package': answers['package'], + 'spec_version': '3.0', + **config_dict, + } config = DipDupYAMLConfig(**config_dict) diff --git a/src/dipdup/config/coinbase.py b/src/dipdup/config/coinbase.py index 41e3d0cfe..c6b8a8f88 100644 --- a/src/dipdup/config/coinbase.py +++ b/src/dipdup/config/coinbase.py @@ -27,3 +27,7 @@ class CoinbaseDatasourceConfig(DatasourceConfig): passphrase: str | None = Field(default=None, repr=False) http: HttpConfig | None = None + + @property + def url(self) -> str: + return 'https://api.pro.coinbase.com' diff --git a/src/dipdup/datasources/coinbase.py b/src/dipdup/datasources/coinbase.py index 2871fd23e..346fe1087 100644 --- a/src/dipdup/datasources/coinbase.py +++ b/src/dipdup/datasources/coinbase.py @@ -11,7 +11,6 @@ from dipdup.models.coinbase import CoinbaseCandleInterval CANDLES_REQUEST_LIMIT = 300 -API_URL = 'https://api.pro.coinbase.com' class CoinbaseDatasource(Datasource[CoinbaseDatasourceConfig]): diff --git a/src/dipdup/project.py b/src/dipdup/project.py index b97342268..adc891660 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -38,9 +38,11 @@ SINGULAR_FORMS = { 'datasources': 'datasource', + 'contracts': 'contract', 'indexes': 'index', 'hooks': 'hook', 'jobs': 'job', + 'runtimes': 'runtime', } # NOTE: All templates are stored in src/dipdup/projects @@ -424,6 +426,8 @@ def fill_config_from_input( section_kinds: tuple[type], filter: str | None, ) -> None: + import survey + section_dict = config.get(section, {}) another = False @@ -431,17 +435,37 @@ def fill_config_from_input( while True: entity_singular = SINGULAR_FORMS[section] - another_str = 'another' if another else 'the first' - add_entity = input(f'Do you want to add {another_str} {entity_singular}? (yes/no): ').strip().lower() + + if another: + another_str = 'another' + choices = ('no', 'yes') + else: + another_str = 'the first' + choices = ( + 'yes', + 'no', + ) + + _, add_entity = prompt_anyof( + f'Do you want to add {another_str} {entity_singular}?', + choices, + ('', ''), + default=0, + ) if add_entity != 'yes': break - # Ask for the entity name - name = input(f'Enter {entity_singular} name: ').strip() + # NOTE: All sections are mappings alias to dict + name = survey.routines.input( + f'Enter {entity_singular} name: ', + ) + + # NOTE: Prepare a filtered (or not) list of entity types # Ask for the type of the entity print(f'Available {entity_singular} kinds:') - for i, entity_type in enumerate(section_kinds, start=1): + matched = {} + for entity_type in section_kinds: try: kind = entity_type.__dataclass_fields__['kind'].default except KeyError: @@ -452,14 +476,23 @@ def fill_config_from_input( if filter and '.' in kind and not kind.startswith(filter): continue - print(f'{i}. {kind}') + matched[kind] = entity_type + + if len(matched) != 1: + kinds = sorted(matched.keys()) + comments = tuple('' for _ in kinds) + print(f'Available {entity_singular} kinds: {kinds} {comments}') - if i == 1: - type_choice = 0 + _, kind = prompt_anyof( + f'Choose {entity_singular} kind: ', + kinds, + comments, + default=0, + ) else: - type_choice = int(input(f'Choose {entity_singular} type (number): ').strip()) - 1 + kind = next(iter(matched)) - entity_model = section_kinds[type_choice] + entity_model = matched[kind] # Gather input for the fields of the chosen type entity_data = {} @@ -476,11 +509,13 @@ def fill_config_from_input( if default == dataclasses.MISSING: default = None elif isinstance(default, FieldInfo): - # else: - # print(default.__dict__) - default = default.default_factory() + default = default.default_factory() if default.default_factory else default.default - field_value = input(f'Enter value for `{field_name}` [{field.type}] ({default}): ').strip() + # field_value = input(f'Enter value for `{field_name}` [{field.type}] ({default}): ').strip() + field_value = survey.routines.input( + f'Enter value for `{field_name}` [{field.type}]: ', + value=default or '', + ) if field_value == '': field_value = default From a4c7da3c39a02b677e558bbdd7cae44b7ceaa82a Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sat, 15 Feb 2025 16:15:59 -0300 Subject: [PATCH 04/30] wip --- src/dipdup/cli.py | 2 +- src/dipdup/config/__init__.py | 29 +++++++++++- src/dipdup/config/_mixin.py | 15 +++++++ src/dipdup/config/coinbase.py | 2 +- src/dipdup/project.py | 84 ++++++++++++++--------------------- 5 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index c1459b81f..dcf6bb102 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -869,7 +869,7 @@ async def new( echo(f'Using template `{template}`\n') config_dict = {} else: - template, config_dict = template_from_terminal() + template, config_dict = template_from_terminal(answers['package']) except Escape: return diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 0020e5b8c..fd9b159b7 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -30,8 +30,10 @@ from typing import Annotated from typing import Any from typing import Literal +from typing import Self from typing import TypeVar from typing import cast +from typing import get_args from urllib.parse import quote_plus import orjson @@ -47,8 +49,10 @@ from dipdup import __spec_version__ from dipdup import env from dipdup.config._mixin import CallbackMixin +from dipdup.config._mixin import InteractiveMixin from dipdup.config._mixin import NameMixin from dipdup.config._mixin import ParentMixin +from dipdup.config._mixin import TerminalOptions from dipdup.exceptions import ConfigInitializationException from dipdup.exceptions import ConfigurationError from dipdup.exceptions import IndexAlreadyExistsError @@ -561,7 +565,7 @@ class AdvancedConfig: @dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) -class DipDupConfig: +class DipDupConfig(InteractiveMixin): """DipDup project configuration file :param spec_version: Version of config specification, currently always `3.0` @@ -617,6 +621,29 @@ def schema_name(self) -> str: def package_path(self) -> Path: return env.get_package_path(self.package) + @classmethod + def from_terminal(cls, opts: TerminalOptions) -> Self: + from dipdup.project import fill_config_from_input + + config_dict = {} + + # NOTE: Substrate or multichain + if opts.namespace in {'substrate', None}: + fill_config_from_input(config_dict, 'runtimes', get_args(RuntimeConfigU), opts.namespace) + + fill_config_from_input(config_dict, 'datasources', get_args(DatasourceConfigU), opts.namespace) + fill_config_from_input(config_dict, 'indexes', get_args(IndexConfigU), opts.namespace) + fill_config_from_input(config_dict, 'hooks', (HookConfig,), None) + fill_config_from_input(config_dict, 'jobs', (JobConfig,), None) + + config_dict = { + 'package': opts.package, + 'spec_version': '3.0', + **config_dict, + } + + return cls(**config_dict) + @classmethod def load( cls, diff --git a/src/dipdup/config/_mixin.py b/src/dipdup/config/_mixin.py index 270e88ca4..98890924d 100644 --- a/src/dipdup/config/_mixin.py +++ b/src/dipdup/config/_mixin.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Generic +from typing import Self from typing import TypeVar from typing import cast @@ -121,3 +122,17 @@ def subgroup_index(self) -> int: @subgroup_index.setter def subgroup_index(self, value: int) -> None: self._subgroup_index = value + + +@dataclass +class TerminalOptions: + package: str + namespace: str | None = None + + +@dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) +class InteractiveMixin: + + @classmethod + @abstractmethod + def from_terminal(cls, opts: TerminalOptions) -> Self: ... diff --git a/src/dipdup/config/coinbase.py b/src/dipdup/config/coinbase.py index c6b8a8f88..a1dc34918 100644 --- a/src/dipdup/config/coinbase.py +++ b/src/dipdup/config/coinbase.py @@ -29,5 +29,5 @@ class CoinbaseDatasourceConfig(DatasourceConfig): http: HttpConfig | None = None @property - def url(self) -> str: + def url(self) -> str: # type: ignore[override] return 'https://api.pro.coinbase.com' diff --git a/src/dipdup/project.py b/src/dipdup/project.py index adc891660..5355393f4 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -9,7 +9,6 @@ from itertools import chain from pathlib import Path from typing import Any -from typing import get_args from pydantic import ConfigDict from pydantic import TypeAdapter @@ -20,12 +19,9 @@ from dipdup import __version__ from dipdup.cli import big_yellow_echo from dipdup.cli import echo -from dipdup.config import DatasourceConfigU -from dipdup.config import HookConfig -from dipdup.config import IndexConfigU -from dipdup.config import JobConfig -from dipdup.config import RuntimeConfigU +from dipdup.config import DipDupConfig from dipdup.config import ToStr +from dipdup.config._mixin import TerminalOptions from dipdup.env import get_package_path from dipdup.env import get_pyproject_name from dipdup.utils import load_template @@ -138,25 +134,7 @@ def get_replay_path(name: str) -> Path: return Path(__file__).parent / 'projects' / name / 'replay.yaml' -def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: - _, mode = prompt_anyof( - question='How would you like to set up your new DipDup project?', - options=( - 'Interactively', - 'From template', - 'Blank', - ), - comments=( - 'Guided setup with prompts', - 'Use one of demo projects', - 'Begin with an empty project', - ), - default=0, - ) - - if mode == 'Blank': - return ('demo_blank', None) - +def namespace_from_terminal() -> str | None: res = prompt_anyof( question='What blockchain are you going to index?', options=( @@ -175,12 +153,38 @@ def template_from_terminal() -> tuple[str | None, dict[str, Any] | None]: ), default=0, ) - namespace = res[1].lower() if res[1] != '[multiple]' else None + return res[1].lower() if res[1] != '[multiple]' else None + + +def template_from_terminal(package: str) -> tuple[str | None, dict[str, Any] | None]: + _, mode = prompt_anyof( + question='How would you like to set up your new DipDup project?', + options=( + 'Interactively', + 'From template', + 'Blank', + ), + comments=( + 'Guided setup with prompts', + 'Use one of demo projects', + 'Begin with an empty project', + ), + default=0, + ) + + if mode == 'Blank': + return ('demo_blank', None) + + namespace = namespace_from_terminal() if mode == 'Interactively': replay_path = get_replay_path('demo_blank') - config_dict = config_from_terminal(namespace) - return ('demo_blank', config_dict) + opts = TerminalOptions( + package=package, + namespace=namespace, + ) + config = DipDupConfig.from_terminal(opts) + return ('demo_blank', config._json) if mode == 'From template': options, comments = [], [] @@ -431,8 +435,6 @@ def fill_config_from_input( section_dict = config.get(section, {}) another = False - print(f'Configuring `{section}` section \n') - while True: entity_singular = SINGULAR_FORMS[section] @@ -463,7 +465,6 @@ def fill_config_from_input( # NOTE: Prepare a filtered (or not) list of entity types # Ask for the type of the entity - print(f'Available {entity_singular} kinds:') matched = {} for entity_type in section_kinds: try: @@ -481,8 +482,6 @@ def fill_config_from_input( if len(matched) != 1: kinds = sorted(matched.keys()) comments = tuple('' for _ in kinds) - print(f'Available {entity_singular} kinds: {kinds} {comments}') - _, kind = prompt_anyof( f'Choose {entity_singular} kind: ', kinds, @@ -514,7 +513,7 @@ def fill_config_from_input( # field_value = input(f'Enter value for `{field_name}` [{field.type}] ({default}): ').strip() field_value = survey.routines.input( f'Enter value for `{field_name}` [{field.type}]: ', - value=default or '', + value=str(default) if default is not None else '', ) if field_value == '': field_value = default @@ -529,7 +528,7 @@ def fill_config_from_input( # Validate and add the entity try: entity_instance = entity_model(**entity_data) - section_dict[name] = entity_instance.__dict__ + section_dict[name] = {k: v for k, v in entity_instance.__dict__.items() if not k.startswith('_')} another = True except Exception as e: print(f'Error: {e}. Please try again.') @@ -559,18 +558,3 @@ def prompt_anyof( index=default, ) return index, options[index] - - -def config_from_terminal(namespace: str | None) -> dict[str, Any]: - config_dict = {} - - # NOTE: Substrate or multichain - if namespace in {'substrate', None}: - fill_config_from_input(config_dict, 'runtimes', get_args(RuntimeConfigU), namespace) - - fill_config_from_input(config_dict, 'datasources', get_args(DatasourceConfigU), namespace) - fill_config_from_input(config_dict, 'indexes', get_args(IndexConfigU), namespace) - fill_config_from_input(config_dict, 'hooks', (HookConfig,), None) - fill_config_from_input(config_dict, 'jobs', (JobConfig,), None) - - return config_dict From bf647d583ccc53b444033c6a186921f3285d5b7a Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sat, 15 Feb 2025 17:12:20 -0300 Subject: [PATCH 05/30] working defaults and bools --- src/dipdup/config/__init__.py | 4 +++ src/dipdup/project.py | 56 +++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index fd9b159b7..891878632 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -295,6 +295,10 @@ class DatasourceConfig(ABC, NameMixin): ws_url: WsUrl | None = None http: HttpConfig | None = None + # @classmethod + # def from_terminal(cls, opts): + # return super().from_terminal(opts) + @dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) class HandlerConfig(CallbackMixin, ParentMixin['IndexConfig']): diff --git a/src/dipdup/project.py b/src/dipdup/project.py index 5355393f4..3eccdc216 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -424,6 +424,20 @@ def _render(answers: Answers, template_path: Path, output_path: Path, force: boo write(output_path, content, overwrite=force) +def prompt_bool( + question: str, + default: bool = False, +) -> bool: + choices = ('yes', 'no') if default else ('no', 'yes') + _, value = prompt_anyof( + question, + choices, + ('', ''), + default=0, + ) + return value == 'yes' + + def fill_config_from_input( config: dict[str, Any], section: str, @@ -437,24 +451,12 @@ def fill_config_from_input( while True: entity_singular = SINGULAR_FORMS[section] + another_str = 'another' if another else 'the first' - if another: - another_str = 'another' - choices = ('no', 'yes') - else: - another_str = 'the first' - choices = ( - 'yes', - 'no', - ) - - _, add_entity = prompt_anyof( + if not prompt_bool( f'Do you want to add {another_str} {entity_singular}?', - choices, - ('', ''), - default=0, - ) - if add_entity != 'yes': + default=not another, + ): break # NOTE: All sections are mappings alias to dict @@ -496,6 +498,8 @@ def fill_config_from_input( # Gather input for the fields of the chosen type entity_data = {} for field_name, field in entity_model.__dataclass_fields__.items(): + # print(field) + if field_name in {'kind', 'http'}: continue if field_name.startswith('_'): @@ -505,26 +509,34 @@ def fill_config_from_input( continue default = field.default + if default == dataclasses.MISSING: default = None elif isinstance(default, FieldInfo): default = default.default_factory() if default.default_factory else default.default - # field_value = input(f'Enter value for `{field_name}` [{field.type}] ({default}): ').strip() field_value = survey.routines.input( f'Enter value for `{field_name}` [{field.type}]: ', value=str(default) if default is not None else '', ) - if field_value == '': - field_value = default + + if field.default_factory == dataclasses.MISSING: + default = None # NOTE: Basic transformations: string lists - if isinstance(field_value, str): - if ',' in field_value: + if 'tuple' in field.type or 'list' in field.type: + if not field_value: + field_value = default + elif ',' in field_value: field_value = field_value.split(',') + else: + field_value = [field_value] entity_data[field_name] = field_value or default + # print(default, 'default') + # print(entity_data[field_name], 'field_value') + # Validate and add the entity try: entity_instance = entity_model(**entity_data) @@ -533,7 +545,7 @@ def fill_config_from_input( except Exception as e: print(f'Error: {e}. Please try again.') - print(section_dict) + # print(section_dict) config[section] = section_dict From dd41b86899ccea11f2d07f8378cb4582f3910edd Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sat, 15 Feb 2025 18:33:21 -0300 Subject: [PATCH 06/30] ref --- src/dipdup/project.py | 131 ++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/src/dipdup/project.py b/src/dipdup/project.py index 3eccdc216..d01c10b49 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -281,29 +281,29 @@ def answers_from_terminal() -> Answers: fg='yellow', ) - big_yellow_echo('Miscellaneous tunables; leave default values if unsure') - - _, answers['package_manager'] = prompt_anyof( - question='Choose package manager', - options=( - 'uv', - 'poetry', - 'pdm', - 'none', - ), - comments=( - 'uv (recommended)', - 'Poetry', - 'PDM', - '[none]', - ), - default=0, - ) - - answers['line_length'] = survey.routines.input( - 'Enter maximum line length for linters: ', - value=answers['line_length'], - ) + # big_yellow_echo('Miscellaneous tunables; leave default values if unsure') + + # _, answers['package_manager'] = prompt_anyof( + # question='Choose package manager', + # options=( + # 'uv', + # 'poetry', + # 'pdm', + # 'none', + # ), + # comments=( + # 'uv (recommended)', + # 'Poetry', + # 'PDM', + # '[none]', + # ), + # default=0, + # ) + + # answers['line_length'] = survey.routines.input( + # 'Enter maximum line length for linters: ', + # value=answers['line_length'], + # ) return answers @@ -438,10 +438,45 @@ def prompt_bool( return value == 'yes' +def prompt_kind( + entity: str, + types: tuple[type, ...], + filter: str | None, +) -> type: + + matched = {} + for entity_type in types: + try: + kind = entity_type.__dataclass_fields__['kind'].default + except KeyError: + kind = entity_type.__name__ + + assert kind + + if filter and '.' in kind and not kind.startswith(filter): + continue + + matched[kind] = entity_type + + if len(matched) == 1: + return next(iter(matched)) + + kinds = sorted(matched.keys()) + comments = tuple('' for _ in kinds) + _, kind = prompt_anyof( + f'Choose {entity} kind: ', + kinds, + comments, + default=0, + ) + return matched[kind] + + + def fill_config_from_input( config: dict[str, Any], section: str, - section_kinds: tuple[type], + types: tuple[type, ...], filter: str | None, ) -> None: import survey @@ -450,54 +485,28 @@ def fill_config_from_input( another = False while True: - entity_singular = SINGULAR_FORMS[section] - another_str = 'another' if another else 'the first' + section_singular = SINGULAR_FORMS[section] if not prompt_bool( - f'Do you want to add {another_str} {entity_singular}?', + f'Do you want to add {'another' if another else 'the first'} {section_singular}?', default=not another, ): break # NOTE: All sections are mappings alias to dict name = survey.routines.input( - f'Enter {entity_singular} name: ', + f'Enter {section_singular} name: ', ) - # NOTE: Prepare a filtered (or not) list of entity types - - # Ask for the type of the entity - matched = {} - for entity_type in section_kinds: - try: - kind = entity_type.__dataclass_fields__['kind'].default - except KeyError: - kind = entity_type.__name__ - - assert kind - - if filter and '.' in kind and not kind.startswith(filter): - continue - - matched[kind] = entity_type - - if len(matched) != 1: - kinds = sorted(matched.keys()) - comments = tuple('' for _ in kinds) - _, kind = prompt_anyof( - f'Choose {entity_singular} kind: ', - kinds, - comments, - default=0, - ) - else: - kind = next(iter(matched)) - - entity_model = matched[kind] + type_ = prompt_kind( + section_singular, + types, + filter, + ) # Gather input for the fields of the chosen type entity_data = {} - for field_name, field in entity_model.__dataclass_fields__.items(): + for field_name, field in type_.__dataclass_fields__.items(): # print(field) if field_name in {'kind', 'http'}: @@ -539,8 +548,8 @@ def fill_config_from_input( # Validate and add the entity try: - entity_instance = entity_model(**entity_data) - section_dict[name] = {k: v for k, v in entity_instance.__dict__.items() if not k.startswith('_')} + obj = type_(**entity_data) + section_dict[name] = {k: v for k, v in obj.__dict__.items() if not k.startswith('_')} another = True except Exception as e: print(f'Error: {e}. Please try again.') From b49200ad9168d4be58f65bf0ffc2d0315039fe88 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sat, 15 Feb 2025 19:00:13 -0300 Subject: [PATCH 07/30] from_terminal with failover --- src/dipdup/config/__init__.py | 55 +++++++++++++-- src/dipdup/project.py | 122 +++++++++++++--------------------- 2 files changed, 93 insertions(+), 84 deletions(-) diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 891878632..74c32262f 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -627,18 +627,59 @@ def package_path(self) -> Path: @classmethod def from_terminal(cls, opts: TerminalOptions) -> Self: - from dipdup.project import fill_config_from_input + import survey - config_dict = {} + from dipdup.project import SINGULAR_FORMS + from dipdup.project import fill_type_from_input + from dipdup.project import prompt_bool + from dipdup.project import prompt_kind + config_dict = defaultdict(dict) + + sections = { + 'datasources': get_args(DatasourceConfigU), + 'runtimes': get_args(RuntimeConfigU), + 'contracts': get_args(ContractConfigU), + # NOTE: Skip the `template` kind + 'indexes': get_args(ResolvedIndexConfigU), + } # NOTE: Substrate or multichain if opts.namespace in {'substrate', None}: - fill_config_from_input(config_dict, 'runtimes', get_args(RuntimeConfigU), opts.namespace) + sections['runtimes'] = get_args(RuntimeConfigU) + + for section, types in sections.items(): + another = False + + while True: + section_singular = SINGULAR_FORMS[section] + + if not prompt_bool( + f'Do you want to add {'another' if another else 'the first'} {section_singular}?', + default=not another, + ): + break + + # NOTE: All sections are mappings alias to dict + name = survey.routines.input( + f'Enter {section_singular} name: ', + ) + + type_ = prompt_kind( + section_singular, + types, + opts.namespace, + ) + + if issubclass(type_, InteractiveMixin): + + res = type_.from_terminal(opts) + else: + _logger.debug('Not an `InteractiveMixin`; falling back to field inspection', type_.__name__) + res = fill_type_from_input(type_) - fill_config_from_input(config_dict, 'datasources', get_args(DatasourceConfigU), opts.namespace) - fill_config_from_input(config_dict, 'indexes', get_args(IndexConfigU), opts.namespace) - fill_config_from_input(config_dict, 'hooks', (HookConfig,), None) - fill_config_from_input(config_dict, 'jobs', (JobConfig,), None) + if res is not None: + config_dict[section][name] = res + another = True config_dict = { 'package': opts.package, diff --git a/src/dipdup/project.py b/src/dipdup/project.py index d01c10b49..592156bcc 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -471,92 +471,60 @@ def prompt_kind( ) return matched[kind] - -def fill_config_from_input( - config: dict[str, Any], - section: str, - types: tuple[type, ...], - filter: str | None, -) -> None: +def fill_type_from_input( + type_: type, +) -> Any: import survey - section_dict = config.get(section, {}) - another = False + # Gather input for the fields of the chosen type + entity_data = {} + for field_name, field in type_.__dataclass_fields__.items(): + # print(field) - while True: - section_singular = SINGULAR_FORMS[section] + if field_name in {'kind', 'http'}: + continue + if field_name.startswith('_'): + continue + if field_name == 'handlers': + entity_data[field_name] = () + continue - if not prompt_bool( - f'Do you want to add {'another' if another else 'the first'} {section_singular}?', - default=not another, - ): - break + default = field.default - # NOTE: All sections are mappings alias to dict - name = survey.routines.input( - f'Enter {section_singular} name: ', - ) + if default == dataclasses.MISSING: + default = None + elif isinstance(default, FieldInfo): + default = default.default_factory() if default.default_factory else default.default - type_ = prompt_kind( - section_singular, - types, - filter, + field_value = survey.routines.input( + f'Enter value for `{field_name}` [{field.type}]: ', + value=str(default) if default is not None else '', ) - # Gather input for the fields of the chosen type - entity_data = {} - for field_name, field in type_.__dataclass_fields__.items(): - # print(field) - - if field_name in {'kind', 'http'}: - continue - if field_name.startswith('_'): - continue - if field_name == 'handlers': - entity_data[field_name] = () - continue - - default = field.default - - if default == dataclasses.MISSING: - default = None - elif isinstance(default, FieldInfo): - default = default.default_factory() if default.default_factory else default.default - - field_value = survey.routines.input( - f'Enter value for `{field_name}` [{field.type}]: ', - value=str(default) if default is not None else '', - ) - - if field.default_factory == dataclasses.MISSING: - default = None - - # NOTE: Basic transformations: string lists - if 'tuple' in field.type or 'list' in field.type: - if not field_value: - field_value = default - elif ',' in field_value: - field_value = field_value.split(',') - else: - field_value = [field_value] - - entity_data[field_name] = field_value or default - - # print(default, 'default') - # print(entity_data[field_name], 'field_value') - - # Validate and add the entity - try: - obj = type_(**entity_data) - section_dict[name] = {k: v for k, v in obj.__dict__.items() if not k.startswith('_')} - another = True - except Exception as e: - print(f'Error: {e}. Please try again.') - - # print(section_dict) - - config[section] = section_dict + if field.default_factory == dataclasses.MISSING: + default = None + + # NOTE: Basic transformations: string lists + if 'tuple' in field.type or 'list' in field.type: + if not field_value: + field_value = default + elif ',' in field_value: + field_value = field_value.split(',') + else: + field_value = [field_value] + + entity_data[field_name] = field_value or default + + # print(default, 'default') + # print(entity_data[field_name], 'field_value') + + # Validate and add the entity + try: + obj = type_(**entity_data) + return {k: v for k, v in obj.__dict__.items() if not k.startswith('_')} + except Exception as e: + print(f'Error: {e}. Please try again.') def prompt_anyof( From 2eb80f534cc55994f7301b58d926ab6e9526f7b2 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sun, 16 Feb 2025 19:38:30 -0300 Subject: [PATCH 08/30] misc fixes --- docs/9.release-notes/1.v8.2.md | 1 - docs/9.release-notes/_8.0_changelog.md | 2 + docs/9.release-notes/_8.2_changelog.md | 2 +- schemas/dipdup-3.0.json | 99 ++++++++++++-------------- src/dipdup/cli.py | 9 +-- src/dipdup/config/__init__.py | 25 +++++-- src/dipdup/config/evm_etherscan.py | 12 ++-- src/dipdup/config/http.py | 3 +- src/dipdup/config/ipfs.py | 3 +- src/dipdup/config/substrate_subscan.py | 3 +- src/dipdup/config/tzip_metadata.py | 3 +- src/dipdup/project.py | 27 +++---- 12 files changed, 101 insertions(+), 88 deletions(-) diff --git a/docs/9.release-notes/1.v8.2.md b/docs/9.release-notes/1.v8.2.md index 8b8bee710..081b6a02e 100644 --- a/docs/9.release-notes/1.v8.2.md +++ b/docs/9.release-notes/1.v8.2.md @@ -44,6 +44,5 @@ DipDup 7.5, our previous major release, has reached end-of-life. We recommend up Going forward, we'll focus on supporting only the latest major version to reduce maintenance overhead. Any breaking changes will be introduced gradually and can be enabled using the `DIPDUP_NEXT` environment variable. - {{ #include 9.release-notes/_8.2_changelog.md }} {{ #include 9.release-notes/_footer.md }} diff --git a/docs/9.release-notes/_8.0_changelog.md b/docs/9.release-notes/_8.0_changelog.md index e0555feed..afde020c6 100644 --- a/docs/9.release-notes/_8.0_changelog.md +++ b/docs/9.release-notes/_8.0_changelog.md @@ -9,6 +9,7 @@ - cli: Added `package verify` command to check the package consistency. - cli: Added full project migration support for 3.0 spec. - cli: Added spec_version 3.0 support to `migrate` command. +- cli: Rewritten interactive mode for `new` command. - config: Publish JSON schemas for config validation and autocompletion. - database: Added `dipdup_status` view to the schema. - env: Added `DIPDUP_JSON_LOG` environment variable to enable JSON logging. @@ -30,6 +31,7 @@ - cli: Fixed progress estimation when there are indexes with `last_level` option set. - cli: Import some dependencies on demand to reduce memory footprint. - cli: Improved logging of indexer status. +- coinbase: Fixed crash when using coinbase datasource. - config: Allow `sentry.dsn` to be empty string. - config: Fixed (de)serialization of hex strings in config. - config: Fixed setting logging levels according to the config. diff --git a/docs/9.release-notes/_8.2_changelog.md b/docs/9.release-notes/_8.2_changelog.md index fa1ece0a8..66e0d7082 100644 --- a/docs/9.release-notes/_8.2_changelog.md +++ b/docs/9.release-notes/_8.2_changelog.md @@ -15,8 +15,8 @@ - cli: Fixed help message on `CallbackError` reporting `batch` handler instead of actual one. - database: Don't process internal models twice if imported from the project. - evm.subsquid: Fixed event/transaction model deserialization. -- starknet: Process all data types correctly. - starknet.node: Fetch missing block timestamp and txn id when synching with node. +- starknet: Process all data types correctly. - substrate.subsquid: Fixed parsing for `__kind` junctions with multiple keys. - substrate.subsquid: Fixed parsing nested structures in response. diff --git a/schemas/dipdup-3.0.json b/schemas/dipdup-3.0.json index 04dc08524..b2df1ad24 100644 --- a/schemas/dipdup-3.0.json +++ b/schemas/dipdup-3.0.json @@ -134,6 +134,7 @@ }, "kind": { "const": "coinbase", + "default": "coinbase", "description": "always 'coinbase'", "title": "kind", "type": "string" @@ -165,9 +166,6 @@ "title": "secret_key" } }, - "required": [ - "kind" - ], "title": "CoinbaseDatasourceConfig", "type": "object" }, @@ -212,6 +210,7 @@ }, "kind": { "const": "evm", + "default": "evm", "description": "Always `evm`", "title": "kind", "type": "string" @@ -230,9 +229,6 @@ "title": "typename" } }, - "required": [ - "kind" - ], "title": "EvmContractConfig", "type": "object" }, @@ -277,17 +273,17 @@ "type": "string" } ], + "default": "evm.etherscan", "description": "always 'evm.etherscan'", "title": "kind" }, "url": { + "$ref": "#/$defs/Url", "description": "API URL", - "title": "url", - "type": "string" + "title": "url" } }, "required": [ - "kind", "url" ], "title": "EvmEtherscanDatasourceConfig", @@ -369,6 +365,7 @@ }, "kind": { "const": "evm.events", + "default": "evm.events", "description": "Always 'evm.events'", "title": "kind", "type": "string" @@ -381,7 +378,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -407,6 +403,7 @@ }, "kind": { "const": "evm.node", + "default": "evm.node", "description": "Always 'evm.node'", "title": "kind", "type": "string" @@ -437,7 +434,6 @@ } }, "required": [ - "kind", "url" ], "title": "EvmNodeDatasourceConfig", @@ -462,6 +458,7 @@ }, "kind": { "const": "evm.subsquid", + "default": "evm.subsquid", "description": "always 'evm.subsquid'", "title": "kind", "type": "string" @@ -473,7 +470,6 @@ } }, "required": [ - "kind", "url" ], "title": "EvmSubsquidDatasourceConfig", @@ -594,6 +590,7 @@ }, "kind": { "const": "evm.transactions", + "default": "evm.transactions", "description": "always 'evm.transactions'", "title": "kind", "type": "string" @@ -606,7 +603,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -957,18 +953,18 @@ }, "kind": { "const": "http", + "default": "http", "description": "always 'http'", "title": "kind", "type": "string" }, "url": { + "$ref": "#/$defs/Url", "description": "URL to fetch data from", - "title": "url", - "type": "string" + "title": "url" } }, "required": [ - "kind", "url" ], "title": "HttpDatasourceConfig", @@ -984,6 +980,13 @@ "title": "first_level", "type": "integer" }, + "kind": { + "const": "template", + "default": "template", + "description": "always 'template'", + "title": "kind", + "type": "string" + }, "last_level": { "default": 0, "description": "Level to stop indexing at", @@ -1027,20 +1030,18 @@ }, "kind": { "const": "ipfs", + "default": "ipfs", "description": "always 'ipfs'", "title": "kind", "type": "string" }, "url": { + "$ref": "#/$defs/Url", "default": "https://ipfs.io/ipfs", "description": "IPFS node URL, e.g. https://ipfs.io/ipfs/", - "title": "url", - "type": "string" + "title": "url" } }, - "required": [ - "kind" - ], "title": "IpfsDatasourceConfig", "type": "object" }, @@ -1136,6 +1137,7 @@ }, "kind": { "const": "postgres", + "default": "postgres", "description": "always 'postgres'", "title": "kind", "type": "string" @@ -1166,7 +1168,6 @@ } }, "required": [ - "kind", "host" ], "title": "PostgresDatabaseConfig", @@ -1324,6 +1325,7 @@ }, "kind": { "const": "sqlite", + "default": "sqlite", "description": "always 'sqlite'", "title": "kind", "type": "string" @@ -1335,9 +1337,6 @@ "type": "string" } }, - "required": [ - "kind" - ], "title": "SqliteDatabaseConfig", "type": "object" }, @@ -1382,6 +1381,7 @@ }, "kind": { "const": "starknet", + "default": "starknet", "description": "Always `starknet`", "title": "kind", "type": "string" @@ -1400,9 +1400,6 @@ "title": "typename" } }, - "required": [ - "kind" - ], "title": "StarknetContractConfig", "type": "object" }, @@ -1479,6 +1476,7 @@ }, "kind": { "const": "starknet.events", + "default": "starknet.events", "description": "Always 'starknet.events'", "title": "kind", "type": "string" @@ -1491,7 +1489,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -1517,6 +1514,7 @@ }, "kind": { "const": "starknet.node", + "default": "starknet.node", "description": "Always 'starknet.node'", "title": "kind", "type": "string" @@ -1547,7 +1545,6 @@ } }, "required": [ - "kind", "url" ], "title": "StarknetNodeDatasourceConfig", @@ -1572,6 +1569,7 @@ }, "kind": { "const": "starknet.subsquid", + "default": "starknet.subsquid", "description": "always 'starknet.subsquid'", "title": "kind", "type": "string" @@ -1583,7 +1581,6 @@ } }, "required": [ - "kind", "url" ], "title": "StarknetSubsquidDatasourceConfig", @@ -1652,6 +1649,7 @@ }, "kind": { "const": "substrate.events", + "default": "substrate.events", "description": "Always 'substrate.events'", "title": "kind", "type": "string" @@ -1676,7 +1674,6 @@ } }, "required": [ - "kind", "datasources", "runtime", "handlers" @@ -1703,6 +1700,7 @@ }, "kind": { "const": "substrate.node", + "default": "substrate.node", "description": "Always 'substrate.node'", "title": "kind", "type": "string" @@ -1727,7 +1725,6 @@ } }, "required": [ - "kind", "url" ], "title": "SubstrateNodeDatasourceConfig", @@ -1793,18 +1790,18 @@ }, "kind": { "const": "substrate.subscan", + "default": "substrate.subscan", "description": "always 'substrate.subscan'", "title": "kind", "type": "string" }, "url": { + "$ref": "#/$defs/Url", "description": "API URL", - "title": "url", - "type": "string" + "title": "url" } }, "required": [ - "kind", "url" ], "title": "SubstrateSubscanDatasourceConfig", @@ -1829,6 +1826,7 @@ }, "kind": { "const": "substrate.subsquid", + "default": "substrate.subsquid", "description": "always 'substrate.subsquid'", "title": "kind", "type": "string" @@ -1840,7 +1838,6 @@ } }, "required": [ - "kind", "url" ], "title": "SubstrateSubsquidDatasourceConfig", @@ -1919,6 +1916,7 @@ }, "kind": { "const": "tezos.big_maps", + "default": "tezos.big_maps", "description": "always 'tezos.big_maps'", "title": "kind", "type": "string" @@ -1937,7 +1935,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -1979,6 +1976,7 @@ }, "kind": { "const": "tezos", + "default": "tezos", "description": "Always `tezos`", "title": "kind", "type": "string" @@ -1997,9 +1995,6 @@ "title": "typename" } }, - "required": [ - "kind" - ], "title": "TezosContractConfig", "type": "object" }, @@ -2080,6 +2075,7 @@ }, "kind": { "const": "tezos.events", + "default": "tezos.events", "description": "always 'tezos.events'", "title": "kind", "type": "string" @@ -2092,7 +2088,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -2154,13 +2149,13 @@ }, "kind": { "const": "tezos.head", + "default": "tezos.head", "description": "always 'tezos.head'", "title": "kind", "type": "string" } }, "required": [ - "kind", "datasources", "callback" ], @@ -2550,6 +2545,7 @@ }, "kind": { "const": "tezos.operations", + "default": "tezos.operations", "description": "always 'tezos.operations'", "title": "kind", "type": "string" @@ -2573,7 +2569,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -2612,6 +2607,7 @@ }, "kind": { "const": "tezos.operations_unfiltered", + "default": "tezos.operations_unfiltered", "description": "always 'tezos.operations_unfiltered'", "title": "kind", "type": "string" @@ -2635,7 +2631,6 @@ } }, "required": [ - "kind", "datasources", "callback" ], @@ -2722,6 +2717,7 @@ }, "kind": { "const": "tezos.token_balances", + "default": "tezos.token_balances", "description": "always 'tezos.token_balances'", "title": "kind", "type": "string" @@ -2734,7 +2730,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -2853,6 +2848,7 @@ }, "kind": { "const": "tezos.token_transfers", + "default": "tezos.token_transfers", "description": "always 'tezos.token_transfers'", "title": "kind", "type": "string" @@ -2865,7 +2861,6 @@ } }, "required": [ - "kind", "datasources", "handlers" ], @@ -2897,6 +2892,7 @@ }, "kind": { "const": "tezos.tzkt", + "default": "tezos.tzkt", "description": "always 'tezos.tzkt'", "title": "kind", "type": "string" @@ -2920,9 +2916,6 @@ "title": "url" } }, - "required": [ - "kind" - ], "title": "TezosTzktDatasourceConfig", "type": "object" }, @@ -2955,6 +2948,7 @@ }, "kind": { "const": "tzip_metadata", + "default": "tzip_metadata", "description": "always 'tzip_metadata'", "title": "kind", "type": "string" @@ -2965,14 +2959,13 @@ "title": "network" }, "url": { + "$ref": "#/$defs/Url", "default": "https://metadata.dipdup.net", "description": "GraphQL API URL, e.g. https://metadata.dipdup.net", - "title": "url", - "type": "string" + "title": "url" } }, "required": [ - "kind", "network" ], "title": "TzipMetadataDatasourceConfig", diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index dcf6bb102..5d054fee3 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -867,7 +867,7 @@ async def new( if template: echo(f'Using template `{template}`\n') - config_dict = {} + config_dict: dict[str, Any] | None = {} else: template, config_dict = template_from_terminal(answers['package']) @@ -884,11 +884,8 @@ async def new( 'spec_version': '3.0', **config_dict, } - - config = DipDupYAMLConfig(**config_dict) - - path = env.get_package_path(config['package']) / ROOT_CONFIG - path.write_text(config.dump()) + path = env.get_package_path(config_dict['package']) / ROOT_CONFIG + path.write_text(DipDupYAMLConfig(**config_dict).dump()) _logger.info('Initializing project') config = DipDupConfig.load([Path(answers['package'])]) diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 74c32262f..32afa0d11 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -75,6 +75,8 @@ def _valid_url(v: str, ws: bool) -> str: + if not v: + raise ConfigurationError('URL is required') if not ws and not v.startswith(('http://', 'https://')): raise ConfigurationError(f'`{v}` is not a valid HTTP URL') if ws and not v.startswith(('ws://', 'wss://')): @@ -627,14 +629,14 @@ def package_path(self) -> Path: @classmethod def from_terminal(cls, opts: TerminalOptions) -> Self: - import survey + import survey # type: ignore[import-untyped] from dipdup.project import SINGULAR_FORMS from dipdup.project import fill_type_from_input from dipdup.project import prompt_bool from dipdup.project import prompt_kind - config_dict = defaultdict(dict) + config_dict: defaultdict[str, dict[str, Any]] = defaultdict(dict) sections = { 'datasources': get_args(DatasourceConfigU), @@ -660,9 +662,18 @@ def from_terminal(cls, opts: TerminalOptions) -> Self: break # NOTE: All sections are mappings alias to dict - name = survey.routines.input( - f'Enter {section_singular} name: ', - ) + name = None + while True: + name = survey.routines.input( + f'Enter {section_singular} name: ', + ) + if not name: + print('Name is required') + continue + if name in config_dict[section]: + print(f'{section_singular.capitalize()} with name `{name}` already exists') + continue + break type_ = prompt_kind( section_singular, @@ -681,13 +692,13 @@ def from_terminal(cls, opts: TerminalOptions) -> Self: config_dict[section][name] = res another = True - config_dict = { + config_dict = { # type: ignore[assignment] 'package': opts.package, 'spec_version': '3.0', **config_dict, } - return cls(**config_dict) + return cls(**config_dict) # type: ignore[arg-type] @classmethod def load( diff --git a/src/dipdup/config/evm_etherscan.py b/src/dipdup/config/evm_etherscan.py index 8e24df9d9..9d68377a2 100644 --- a/src/dipdup/config/evm_etherscan.py +++ b/src/dipdup/config/evm_etherscan.py @@ -6,8 +6,11 @@ from pydantic import ConfigDict from pydantic.dataclasses import dataclass +from dipdup import env from dipdup.config import DatasourceConfig from dipdup.config import HttpConfig +from dipdup.config import Url +from dipdup.exceptions import ConfigurationError _logger = logging.getLogger(__name__) @@ -24,13 +27,14 @@ class EvmEtherscanDatasourceConfig(DatasourceConfig): # NOTE: Alias, remove in 9.0 kind: Literal['evm.etherscan'] | Literal['abi.etherscan'] = 'evm.etherscan' - url: str + url: Url api_key: str | None = None http: HttpConfig | None = None def __post_init__(self) -> None: if self.kind == 'abi.etherscan': - _logger.warning( - '`abi.etherscan` datasource has been renamed to `evm.etherscan`. Please, update your config.' - ) + msg = '`abi.etherscan` datasource has been renamed to `evm.etherscan`. Please, update your config.' + if env.NEXT: + raise ConfigurationError(msg) + _logger.warning(msg) diff --git a/src/dipdup/config/http.py b/src/dipdup/config/http.py index b4341e90e..772e7c918 100644 --- a/src/dipdup/config/http.py +++ b/src/dipdup/config/http.py @@ -7,6 +7,7 @@ from dipdup.config import DatasourceConfig from dipdup.config import HttpConfig +from dipdup.config import Url @dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) @@ -19,5 +20,5 @@ class HttpDatasourceConfig(DatasourceConfig): """ kind: Literal['http'] = 'http' - url: str + url: Url http: HttpConfig | None = None diff --git a/src/dipdup/config/ipfs.py b/src/dipdup/config/ipfs.py index 90993b647..df1bc962d 100644 --- a/src/dipdup/config/ipfs.py +++ b/src/dipdup/config/ipfs.py @@ -7,6 +7,7 @@ from dipdup.config import DatasourceConfig from dipdup.config import HttpConfig +from dipdup.config import Url DEFAULT_IPFS_URL = 'https://ipfs.io/ipfs' @@ -21,5 +22,5 @@ class IpfsDatasourceConfig(DatasourceConfig): """ kind: Literal['ipfs'] = 'ipfs' - url: str = DEFAULT_IPFS_URL + url: Url = DEFAULT_IPFS_URL http: HttpConfig | None = None diff --git a/src/dipdup/config/substrate_subscan.py b/src/dipdup/config/substrate_subscan.py index 8167da3b4..296ab629e 100644 --- a/src/dipdup/config/substrate_subscan.py +++ b/src/dipdup/config/substrate_subscan.py @@ -7,6 +7,7 @@ from dipdup.config import DatasourceConfig from dipdup.config import HttpConfig +from dipdup.config import Url @dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) @@ -20,7 +21,7 @@ class SubstrateSubscanDatasourceConfig(DatasourceConfig): """ kind: Literal['substrate.subscan'] = 'substrate.subscan' - url: str + url: Url api_key: str | None = None http: HttpConfig | None = None diff --git a/src/dipdup/config/tzip_metadata.py b/src/dipdup/config/tzip_metadata.py index b787fde43..52665c4db 100644 --- a/src/dipdup/config/tzip_metadata.py +++ b/src/dipdup/config/tzip_metadata.py @@ -7,6 +7,7 @@ from dipdup.config import DatasourceConfig from dipdup.config import HttpConfig +from dipdup.config import Url from dipdup.models.tzip_metadata import TzipMetadataNetwork DEFAULT_TZIP_METADATA_URL = 'https://metadata.dipdup.net' @@ -24,5 +25,5 @@ class TzipMetadataDatasourceConfig(DatasourceConfig): kind: Literal['tzip_metadata'] = 'tzip_metadata' network: TzipMetadataNetwork - url: str = DEFAULT_TZIP_METADATA_URL + url: Url = DEFAULT_TZIP_METADATA_URL http: HttpConfig | None = None diff --git a/src/dipdup/project.py b/src/dipdup/project.py index 592156bcc..ae5618053 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -135,7 +135,7 @@ def get_replay_path(name: str) -> Path: def namespace_from_terminal() -> str | None: - res = prompt_anyof( + _, res = prompt_anyof( question='What blockchain are you going to index?', options=( 'EVM', @@ -153,7 +153,10 @@ def namespace_from_terminal() -> str | None: ), default=0, ) - return res[1].lower() if res[1] != '[multiple]' else None + print(res) + if res == '[multiple]': + return None + return res.lower() def template_from_terminal(package: str) -> tuple[str | None, dict[str, Any] | None]: @@ -190,7 +193,7 @@ def template_from_terminal(package: str) -> tuple[str | None, dict[str, Any] | N options, comments = [], [] templates = TEMPLATES[namespace] if namespace else chain(v for v in TEMPLATES.values()) for name in templates: - replay_path = get_replay_path(name) + replay_path = get_replay_path(name) # type: ignore[arg-type] _answers = answers_from_replay(replay_path) options.append(_answers['template']) comments.append(_answers['description']) @@ -233,10 +236,10 @@ def answers_from_terminal() -> Answers: ) answers['package'] = package - answers['version'] = survey.routines.input( - 'Enter project version: ', - value=answers['version'], - ) + # answers['version'] = survey.routines.input( + # 'Enter project version: ', + # value=answers['version'], + # ) # NOTE: Used in pyproject.toml, README.md and some other places answers['description'] = survey.routines.input( @@ -447,7 +450,7 @@ def prompt_kind( matched = {} for entity_type in types: try: - kind = entity_type.__dataclass_fields__['kind'].default + kind = entity_type.__dataclass_fields__['kind'].default # type: ignore[attr-defined] except KeyError: kind = entity_type.__name__ @@ -459,9 +462,9 @@ def prompt_kind( matched[kind] = entity_type if len(matched) == 1: - return next(iter(matched)) + return next(iter(matched)) # type: ignore[no-any-return] - kinds = sorted(matched.keys()) + kinds = tuple(sorted(matched.keys())) comments = tuple('' for _ in kinds) _, kind = prompt_anyof( f'Choose {entity} kind: ', @@ -479,7 +482,7 @@ def fill_type_from_input( # Gather input for the fields of the chosen type entity_data = {} - for field_name, field in type_.__dataclass_fields__.items(): + for field_name, field in type_.__dataclass_fields__.items(): # type: ignore[attr-defined] # print(field) if field_name in {'kind', 'http'}: @@ -495,7 +498,7 @@ def fill_type_from_input( if default == dataclasses.MISSING: default = None elif isinstance(default, FieldInfo): - default = default.default_factory() if default.default_factory else default.default + default = default.default_factory() if default.default_factory else default.default # type: ignore[call-arg] field_value = survey.routines.input( f'Enter value for `{field_name}` [{field.type}]: ', From afab55fda92a73adfdccaeae8a31ca8e6f7ff2ab Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Mon, 17 Feb 2025 08:52:56 -0300 Subject: [PATCH 09/30] bump dmcg --- requirements.txt | 30 ++++---- src/dipdup/config/__init__.py | 8 +- uv.lock | 138 +++++++++++++++++----------------- 3 files changed, 89 insertions(+), 87 deletions(-) diff --git a/requirements.txt b/requirements.txt index 97acc296b..8dd56fada 100644 --- a/requirements.txt +++ b/requirements.txt @@ -172,9 +172,9 @@ cytoolz==1.0.1 ; implementation_name == 'cpython' \ --hash=sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0 \ --hash=sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9 \ --hash=sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4 -datamodel-code-generator==0.27.2 \ - --hash=sha256:1a7655f5fd3a61329b57534904f5c40dd850850e420696fd946ec7a4f59c32b8 \ - --hash=sha256:efcbfbe6a1488d3411fc588b1ce1af5f854f5107810b1cc9026a6d6333a7c4d8 +datamodel-code-generator==0.28.1 \ + --hash=sha256:1ff8a56f9550a82bcba3e1ad7ebdb89bc655eeabbc4bc6acfb05977cbdc6381c \ + --hash=sha256:37ef5f3b488f7d7a3f0b5b3ba0f2bc1ae01bab4dc7e0f6b99ff6c40713a6beb3 dictdiffer==0.9.0 \ --hash=sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578 \ --hash=sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595 @@ -190,9 +190,9 @@ eth-account==0.13.3 \ eth-hash==0.7.1 \ --hash=sha256:0fb1add2adf99ef28883fd6228eb447ef519ea72933535ad1a0b28c6f65f868a \ --hash=sha256:d2411a403a0b0a62e8247b4117932d900ffb4c8c64b15f92620547ca5ce46be5 -eth-keyfile==0.9.0 \ - --hash=sha256:45d3513b6433ad885370225ba0429ed26493ba23589c5b1ca5da024765020fef \ - --hash=sha256:8621c35e83cbc05909d2f23dbb8a87633918733caea553ae0e298f6a06291526 +eth-keyfile==0.9.1 \ + --hash=sha256:9789c3b4fa0bb6e2616cdc2bdd71b8755b42947d78ef1e900a0149480fabb5c2 \ + --hash=sha256:c7a8bc6af4527d1ab2eb1d1b949d59925252e17663eaf90087da121327b51df6 eth-keys==0.6.1 \ --hash=sha256:7deae4cd56e862e099ec58b78176232b931c4ea5ecded2f50c7b1ccbc10c24cf \ --hash=sha256:a43e263cbcabfd62fa769168efc6c27b1f5603040e4de22bb84d12567e4fd962 @@ -499,9 +499,9 @@ ruamel-yaml-clib==0.2.12 ; platform_python_implementation == 'CPython' \ scalecodec==1.2.11 \ --hash=sha256:99a2cdbfccdcaf22bd86b86da55a730a2855514ad2309faef4a4a93ac6cbeb8d \ --hash=sha256:d15c94965f617caa25096f83a45f5f73031d05e6ee08d6039969f0a64fc35de1 -sentry-sdk==2.20.0 \ - --hash=sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab \ - --hash=sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364 +sentry-sdk==2.21.0 \ + --hash=sha256:7623cfa9e2c8150948a81ca253b8e2bfe4ce0b96ab12f8cd78e3ac9c490fd92f \ + --hash=sha256:a6d38e0fb35edda191acf80b188ec713c863aaa5ad8d5798decb8671d02077b6 six==1.17.0 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 @@ -530,9 +530,9 @@ toolz==1.0.0 ; implementation_name == 'cpython' or implementation_name == 'pypy' tortoise-orm==0.24.0 \ --hash=sha256:ae0704a93ea27931724fc899e57268c8081afce3b32b110b00037ec206553e7d \ --hash=sha256:ee3b72b226767293b24c5c4906ae5f027d7cc84496cd503352c918564b4fd687 -typeguard==4.4.1 \ - --hash=sha256:0d22a89d00b453b47c49875f42b6601b961757541a2e1e0ef517b6e24213c21b \ - --hash=sha256:9324ec07a27ec67fc54a9c063020ca4c0ae6abad5e9f0f9804ca59aee68c6e21 +typeguard==4.4.2 \ + --hash=sha256:77a78f11f09777aeae7fa08585f33b5f4ef0e7335af40005b0c422ed398ff48c \ + --hash=sha256:a6f1065813e32ef365bc3b3f503af8a96f9dd4e0033a02c28c4a4983de8c6c49 types-requests==2.32.0.20241016 \ --hash=sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95 \ --hash=sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747 @@ -545,9 +545,9 @@ typing-inspect==0.9.0 \ tzdata==2025.1 ; sys_platform == 'win32' \ --hash=sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694 \ --hash=sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639 -tzlocal==5.2 \ - --hash=sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8 \ - --hash=sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e +tzlocal==5.3 \ + --hash=sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2 \ + --hash=sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c urllib3==2.3.0 \ --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 32afa0d11..48a5a34b7 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -692,13 +692,16 @@ def from_terminal(cls, opts: TerminalOptions) -> Self: config_dict[section][name] = res another = True + # NOTE: Make sure that header is above other sections config_dict = { # type: ignore[assignment] 'package': opts.package, 'spec_version': '3.0', **config_dict, } - return cls(**config_dict) # type: ignore[arg-type] + self = cls(**config_dict) # type: ignore[arg-type] + self._json = config_dict # type: ignore[assignment] + return self @classmethod def load( @@ -716,9 +719,6 @@ def load( ) try: - # from pydantic.dataclasses import rebuild_dataclass - # rebuild_dataclass(cls, force=True) - config = TypeAdapter(cls).validate_python(config_json) except ConfigurationError: raise diff --git a/uv.lock b/uv.lock index 922958893..4372a6db0 100644 --- a/uv.lock +++ b/uv.lock @@ -376,19 +376,21 @@ wheels = [ [[package]] name = "coverage" -version = "7.6.11" +version = "7.6.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/4e/38141d42af7452f4b7c5d3d7442a8018de34754ef52eb9a400768bc8d59e/coverage-7.6.11.tar.gz", hash = "sha256:e642e6a46a04e992ebfdabed79e46f478ec60e2c528e1e1a074d63800eda4286", size = 805460 } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/83/cf3d6ac06bd02e1fb7fc6609d7a3be799328a94938dd2a64cf091989b8ce/coverage-7.6.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbb1a822fd858d9853333a7c95d4e70dde9a79e65893138ce32c2ec6457d7a36", size = 208543 }, - { url = "https://files.pythonhosted.org/packages/e7/e1/b1448995072ab033898758179e208afa924f4625ea4524ec868fafbae77d/coverage-7.6.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61c834cbb80946d6ebfddd9b393a4c46bec92fcc0fa069321fcb8049117f76ea", size = 208805 }, - { url = "https://files.pythonhosted.org/packages/80/22/11ae7726086bf16ad35ecd1ebf31c0c709647b2618977bc088003bd38808/coverage-7.6.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a46d56e99a31d858d6912d31ffa4ede6a325c86af13139539beefca10a1234ce", size = 239768 }, - { url = "https://files.pythonhosted.org/packages/7d/68/717286bda6530f39f3ac16899dac1855a71921aca5ee565484269326c979/coverage-7.6.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b48db06f53d1864fea6dbd855e6d51d41c0f06c212c3004511c0bdc6847b297", size = 242023 }, - { url = "https://files.pythonhosted.org/packages/93/57/4b028c7c882411d9ca3f12cd4223ceeb5cb39f84bb91c4fb21a06440cbd9/coverage-7.6.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6ff5be3b1853e0862da9d349fe87f869f68e63a25f7c37ce1130b321140f963", size = 239610 }, - { url = "https://files.pythonhosted.org/packages/44/88/720c9eba316406f243670237306bcdb8e269e4d0e12b191a697f66369404/coverage-7.6.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be05bde21d5e6eefbc3a6de6b9bee2b47894b8945342e8663192809c4d1f08ce", size = 241212 }, - { url = "https://files.pythonhosted.org/packages/1d/ae/a09edf77bd535d597de13679262845f5cb6ff1fab37a3065640fb3d5e6e8/coverage-7.6.11-cp312-cp312-win32.whl", hash = "sha256:e3b746fa0ffc5b6b8856529de487da8b9aeb4fb394bb58de6502ef45f3434f12", size = 211186 }, - { url = "https://files.pythonhosted.org/packages/80/5d/63ad5e3f1421504194da0228d259a3913884830999d1297b5e16b59bcb0f/coverage-7.6.11-cp312-cp312-win_amd64.whl", hash = "sha256:ac476e6d0128fb7919b3fae726de72b28b5c9644cb4b579e4a523d693187c551", size = 211974 }, - { url = "https://files.pythonhosted.org/packages/24/f3/63cd48409a519d4f6cf79abc6c89103a8eabc5c93e496f40779269dba0c0/coverage-7.6.11-py3-none-any.whl", hash = "sha256:f0f334ae844675420164175bf32b04e18a81fe57ad8eb7e0cfd4689d681ffed7", size = 200446 }, + { url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 }, + { url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 }, + { url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 }, + { url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 }, + { url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 }, + { url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 }, + { url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 }, + { url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 }, + { url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 }, + { url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 }, ] [[package]] @@ -444,7 +446,7 @@ wheels = [ [[package]] name = "datamodel-code-generator" -version = "0.27.2" +version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -457,9 +459,9 @@ dependencies = [ { name = "pydantic" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/49/9cb4f868856304dd4e2fc0795d848889a7c9c6f2539165ad24977cef0da3/datamodel_code_generator-0.27.2.tar.gz", hash = "sha256:1a7655f5fd3a61329b57534904f5c40dd850850e420696fd946ec7a4f59c32b8", size = 436345 } +sdist = { url = "https://files.pythonhosted.org/packages/cb/d3/80f6a2394bbf3b46b150fc75afa5b0050f91baa5771e9be87df148013d83/datamodel_code_generator-0.28.1.tar.gz", hash = "sha256:37ef5f3b488f7d7a3f0b5b3ba0f2bc1ae01bab4dc7e0f6b99ff6c40713a6beb3", size = 434901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/a0/678f10ecc40f1cce3c170246c3dd1b86735867d2844eb9f4596abf187dac/datamodel_code_generator-0.27.2-py3-none-any.whl", hash = "sha256:efcbfbe6a1488d3411fc588b1ce1af5f854f5107810b1cc9026a6d6333a7c4d8", size = 115483 }, + { url = "https://files.pythonhosted.org/packages/c8/17/2876ca0a4ac7dd7cb5f56a2f0f6d9ac910969f467e8142c847c45a76b897/datamodel_code_generator-0.28.1-py3-none-any.whl", hash = "sha256:1ff8a56f9550a82bcba3e1ad7ebdb89bc655eeabbc4bc6acfb05977cbdc6381c", size = 115601 }, ] [[package]] @@ -684,7 +686,7 @@ pycryptodome = [ [[package]] name = "eth-keyfile" -version = "0.9.0" +version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "eth-keys" }, @@ -692,9 +694,9 @@ dependencies = [ { name = "py-ecc" }, { name = "pycryptodome" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/fc/1b957ce3403158417b8e632c87ceffb0c2b0845505c280571e23ae57bff3/eth_keyfile-0.9.0.tar.gz", hash = "sha256:8621c35e83cbc05909d2f23dbb8a87633918733caea553ae0e298f6a06291526", size = 19793 } +sdist = { url = "https://files.pythonhosted.org/packages/08/e4/3f0c20b020786e1fa6e1ecd81806c54167fa2b0839e0020086b95a6e8faf/eth_keyfile-0.9.1.tar.gz", hash = "sha256:c7a8bc6af4527d1ab2eb1d1b949d59925252e17663eaf90087da121327b51df6", size = 19787 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/5f/854a4c2bb7184b69947b77ed1337009ad3e57e085dc7b2010dc13d868d4a/eth_keyfile-0.9.0-py3-none-any.whl", hash = "sha256:45d3513b6433ad885370225ba0429ed26493ba23589c5b1ca5da024765020fef", size = 9869 }, + { url = "https://files.pythonhosted.org/packages/5f/08/9c8bf617b39e1dd56303593292e8b4eb66497a5f0f5b997a4b291e5343c0/eth_keyfile-0.9.1-py3-none-any.whl", hash = "sha256:9789c3b4fa0bb6e2616cdc2bdd71b8755b42947d78ef1e900a0149480fabb5c2", size = 9866 }, ] [[package]] @@ -1066,20 +1068,20 @@ wheels = [ [[package]] name = "numpy" -version = "2.2.2" +version = "2.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/d0/c12ddfd3a02274be06ffc71f3efc6d0e457b0409c4481596881e748cb264/numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f", size = 20233295 } +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/e6/847d15770ab7a01e807bdfcd4ead5bdae57c0092b7dc83878171b6af97bb/numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467", size = 20912636 }, - { url = "https://files.pythonhosted.org/packages/d1/af/f83580891577b13bd7e261416120e036d0d8fb508c8a43a73e38928b794b/numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a", size = 14098403 }, - { url = "https://files.pythonhosted.org/packages/2b/86/d019fb60a9d0f1d4cf04b014fe88a9135090adfadcc31c1fadbb071d7fa7/numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825", size = 5128938 }, - { url = "https://files.pythonhosted.org/packages/7a/1b/50985edb6f1ec495a1c36452e860476f5b7ecdc3fc59ea89ccad3c4926c5/numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37", size = 6661937 }, - { url = "https://files.pythonhosted.org/packages/f4/1b/17efd94cad1b9d605c3f8907fb06bcffc4ce4d1d14d46b95316cccccf2b9/numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748", size = 14049518 }, - { url = "https://files.pythonhosted.org/packages/5b/73/65d2f0b698df1731e851e3295eb29a5ab8aa06f763f7e4188647a809578d/numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0", size = 16099146 }, - { url = "https://files.pythonhosted.org/packages/d5/69/308f55c0e19d4b5057b5df286c5433822e3c8039ede06d4051d96f1c2c4e/numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278", size = 15246336 }, - { url = "https://files.pythonhosted.org/packages/f0/d8/d8d333ad0d8518d077a21aeea7b7c826eff766a2b1ce1194dea95ca0bacf/numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba", size = 17863507 }, - { url = "https://files.pythonhosted.org/packages/82/6e/0b84ad3103ffc16d6673e63b5acbe7901b2af96c2837174c6318c98e27ab/numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283", size = 6276491 }, - { url = "https://files.pythonhosted.org/packages/fc/84/7f801a42a67b9772a883223a0a1e12069a14626c81a732bd70aac57aebc1/numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb", size = 12616372 }, + { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, + { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, + { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, + { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, + { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, + { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, ] [[package]] @@ -1217,17 +1219,17 @@ wheels = [ [[package]] name = "psutil" -version = "6.1.1" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502 } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511 }, - { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985 }, - { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488 }, - { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477 }, - { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017 }, - { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602 }, - { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, ] [[package]] @@ -1569,27 +1571,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/74/6c359f6b9ed85b88df6ef31febce18faeb852f6c9855651dfb1184a46845/ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c", size = 3634177 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/4b/82b7c9ac874e72b82b19fd7eab57d122e2df44d2478d90825854f9232d02/ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442", size = 11681264 }, - { url = "https://files.pythonhosted.org/packages/27/5c/f5ae0a9564e04108c132e1139d60491c0abc621397fe79a50b3dc0bd704b/ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a", size = 11657554 }, - { url = "https://files.pythonhosted.org/packages/2a/83/c6926fa3ccb97cdb3c438bb56a490b395770c750bf59f9bc1fe57ae88264/ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36", size = 11088959 }, - { url = "https://files.pythonhosted.org/packages/af/a7/42d1832b752fe969ffdbfcb1b4cb477cb271bed5835110fb0a16ef31ab81/ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001", size = 11902041 }, - { url = "https://files.pythonhosted.org/packages/53/cf/1fffa09fb518d646f560ccfba59f91b23c731e461d6a4dedd21a393a1ff1/ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b", size = 11421069 }, - { url = "https://files.pythonhosted.org/packages/09/27/bb8f1b7304e2a9431f631ae7eadc35550fe0cf620a2a6a0fc4aa3d736f94/ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070", size = 12625095 }, - { url = "https://files.pythonhosted.org/packages/d7/ce/ab00bc9d3df35a5f1b64f5117458160a009f93ae5caf65894ebb63a1842d/ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440", size = 13257797 }, - { url = "https://files.pythonhosted.org/packages/88/81/c639a082ae6d8392bc52256058ec60f493c6a4d06d5505bccface3767e61/ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80", size = 12763793 }, - { url = "https://files.pythonhosted.org/packages/b3/d0/0a3d8f56d1e49af466dc770eeec5c125977ba9479af92e484b5b0251ce9c/ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393", size = 14386234 }, - { url = "https://files.pythonhosted.org/packages/04/70/e59c192a3ad476355e7f45fb3a87326f5219cc7c472e6b040c6c6595c8f0/ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2", size = 12437505 }, - { url = "https://files.pythonhosted.org/packages/55/4e/3abba60a259d79c391713e7a6ccabf7e2c96e5e0a19100bc4204f1a43a51/ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee", size = 11884799 }, - { url = "https://files.pythonhosted.org/packages/a3/db/b0183a01a9f25b4efcae919c18fb41d32f985676c917008620ad692b9d5f/ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1", size = 11527411 }, - { url = "https://files.pythonhosted.org/packages/0a/e4/3ebfcebca3dff1559a74c6becff76e0b64689cea02b7aab15b8b32ea245d/ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a", size = 12078868 }, - { url = "https://files.pythonhosted.org/packages/ec/b2/5ab808833e06c0a1b0d046a51c06ec5687b73c78b116e8d77687dc0cd515/ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5", size = 12524374 }, - { url = "https://files.pythonhosted.org/packages/e0/51/1432afcc3b7aa6586c480142caae5323d59750925c3559688f2a9867343f/ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723", size = 9853682 }, - { url = "https://files.pythonhosted.org/packages/b7/ad/c7a900591bd152bb47fc4882a27654ea55c7973e6d5d6396298ad3fd6638/ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6", size = 10865744 }, - { url = "https://files.pythonhosted.org/packages/75/d9/fde7610abd53c0c76b6af72fc679cb377b27c617ba704e25da834e0a0608/ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9", size = 10064595 }, +version = "0.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 }, + { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 }, + { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 }, + { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 }, + { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 }, + { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 }, + { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 }, + { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 }, + { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 }, + { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 }, + { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 }, + { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 }, + { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 }, + { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 }, + { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 }, + { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 }, + { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 }, ] [[package]] @@ -1630,15 +1632,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.20.0" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/e8/6a366c0cd5e129dda6ecb20ff097f70b18182c248d4c27e813c21f98992a/sentry_sdk-2.20.0.tar.gz", hash = "sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab", size = 300125 } +sdist = { url = "https://files.pythonhosted.org/packages/08/63/3f0e88709cf4af992e2813c27d8ba628a891db0805e3fcc6dc834e142c5b/sentry_sdk-2.21.0.tar.gz", hash = "sha256:a6d38e0fb35edda191acf80b188ec713c863aaa5ad8d5798decb8671d02077b6", size = 301965 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/0f/6f7e6cd0f4a141752caef3f79300148422fdf2b8b68b531f30b2b0c0cbda/sentry_sdk-2.20.0-py2.py3-none-any.whl", hash = "sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364", size = 322576 }, + { url = "https://files.pythonhosted.org/packages/a4/18/7587660cb5e4d07134913d8e74137efcd4903fda873bf612c30eb34c7ab4/sentry_sdk-2.21.0-py2.py3-none-any.whl", hash = "sha256:7623cfa9e2c8150948a81ca253b8e2bfe4ce0b96ab12f8cd78e3ac9c490fd92f", size = 324096 }, ] [[package]] @@ -1867,14 +1869,14 @@ wheels = [ [[package]] name = "typeguard" -version = "4.4.1" +version = "4.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/c3/400917dd37d7b8c07e9723f3046818530423e1e759a56a22133362adab00/typeguard-4.4.1.tar.gz", hash = "sha256:0d22a89d00b453b47c49875f42b6601b961757541a2e1e0ef517b6e24213c21b", size = 74959 } +sdist = { url = "https://files.pythonhosted.org/packages/70/60/8cd6a3d78d00ceeb2193c02b7ed08f063d5341ccdfb24df88e61f383048e/typeguard-4.4.2.tar.gz", hash = "sha256:a6f1065813e32ef365bc3b3f503af8a96f9dd4e0033a02c28c4a4983de8c6c49", size = 75746 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/53/9465dedf2d69fe26008e7732cf6e0a385e387c240869e7d54eed49782a3c/typeguard-4.4.1-py3-none-any.whl", hash = "sha256:9324ec07a27ec67fc54a9c063020ca4c0ae6abad5e9f0f9804ca59aee68c6e21", size = 35635 }, + { url = "https://files.pythonhosted.org/packages/cf/4b/9a77dc721aa0b7f74440a42e4ef6f9a4fae7324e17f64f88b96f4c25cc05/typeguard-4.4.2-py3-none-any.whl", hash = "sha256:77a78f11f09777aeae7fa08585f33b5f4ef0e7335af40005b0c422ed398ff48c", size = 35801 }, ] [[package]] @@ -1940,14 +1942,14 @@ wheels = [ [[package]] name = "tzlocal" -version = "5.2" +version = "5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 } +sdist = { url = "https://files.pythonhosted.org/packages/33/cc/11360404b20a6340b9b4ed39a3338c4af47bc63f87f6cea94dbcbde07029/tzlocal-5.3.tar.gz", hash = "sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2", size = 30480 } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/3f/c4c51c55ff8487f2e6d0e618dba917e3c3ee2caae6cf0fbb59c9b1876f2e/tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8", size = 17859 }, + { url = "https://files.pythonhosted.org/packages/e9/9f/1c0b69d3abf4c65acac051ad696b8aea55afbb746dea8017baab53febb5e/tzlocal-5.3-py3-none-any.whl", hash = "sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c", size = 17920 }, ] [[package]] From d51f7e6f6572e64946bc0821fa9c6809b96c25d2 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Mon, 17 Feb 2025 10:53:45 -0300 Subject: [PATCH 10/30] fix CI --- src/dipdup/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 5d054fee3..de16b8048 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -852,6 +852,8 @@ async def new( from dipdup.project import render_project from dipdup.project import template_from_terminal + config_dict: dict[str, Any] = {} + if quiet: answers = get_default_answers() if template: @@ -867,7 +869,6 @@ async def new( if template: echo(f'Using template `{template}`\n') - config_dict: dict[str, Any] | None = {} else: template, config_dict = template_from_terminal(answers['package']) From 58e1c477b21905d9175d6b049d7b2ff199e71494 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Tue, 18 Feb 2025 07:12:40 -0300 Subject: [PATCH 11/30] default order --- src/dipdup/cli.py | 2 +- src/dipdup/project.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index de16b8048..9e210da44 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -852,7 +852,7 @@ async def new( from dipdup.project import render_project from dipdup.project import template_from_terminal - config_dict: dict[str, Any] = {} + config_dict: dict[str, Any] | None = None if quiet: answers = get_default_answers() diff --git a/src/dipdup/project.py b/src/dipdup/project.py index ae5618053..5fa5eba4c 100644 --- a/src/dipdup/project.py +++ b/src/dipdup/project.py @@ -163,13 +163,13 @@ def template_from_terminal(package: str) -> tuple[str | None, dict[str, Any] | N _, mode = prompt_anyof( question='How would you like to set up your new DipDup project?', options=( - 'Interactively', 'From template', + 'Interactively', 'Blank', ), comments=( - 'Guided setup with prompts', 'Use one of demo projects', + 'Guided setup with prompts', 'Begin with an empty project', ), default=0, From 53d9a95a97c788524092fea9bfbd5624a175d791 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Wed, 19 Feb 2025 10:48:31 -0300 Subject: [PATCH 12/30] MCP WIP --- indexer_state.py | 20 +++++ pyproject.toml | 7 +- src/demo_tezos_auction/dipdup.yaml | 17 ++-- src/dipdup/api.py | 30 ++++++++ src/dipdup/cli.py | 30 ++++++++ src/dipdup/config/__init__.py | 13 ++++ src/dipdup/dipdup.py | 2 + uv.lock | 120 ++++++++++++++++++++++++++++- 8 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 indexer_state.py diff --git a/indexer_state.py b/indexer_state.py new file mode 100644 index 000000000..f680d7f31 --- /dev/null +++ b/indexer_state.py @@ -0,0 +1,20 @@ +from headmcp import HeadMCP + +def get_indexer_state(): + # Initialize Head MCP client + head_mcp = HeadMCP() + + # Fetch the current indexer state + indexer_state = head_mcp.get_indexer_state() + + return indexer_state + +def main(): + try: + state = get_indexer_state() + print(f"Current Indexer State: {state}") + except Exception as e: + print(f"Error fetching indexer state: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4a3776d53..b314eab87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ dependencies = [ "datamodel-code-generator~=0.26", "eth-abi~=5.0", "lru-dict~=1.3", + "mcp>=1.2.1", "orjson~=3.10", "prometheus-client~=0.20", "pycryptodome~=3.20", @@ -68,15 +69,15 @@ dependencies = [ "pyhumps~=3.8", "pysignalr~=1.0", "python-dotenv~=1.0", - "python-json-logger~=2.0", # pinned + "python-json-logger~=2.0", # pinned "ruamel.yaml~=0.18.6", "sentry-sdk~=2.16", "sqlparse~=0.5", - "starknet-py~=0.25.0", # pinned + "starknet-py~=0.25.0", # pinned "strict-rfc3339~=0.7", "survey~=5.4", "tabulate~=0.9", - "tortoise-orm==0.24.0", # pinned + "tortoise-orm==0.24.0", # pinned "uvloop~=0.20", "web3~=7.2", ] diff --git a/src/demo_tezos_auction/dipdup.yaml b/src/demo_tezos_auction/dipdup.yaml index 689bd930b..12f897282 100644 --- a/src/demo_tezos_auction/dipdup.yaml +++ b/src/demo_tezos_auction/dipdup.yaml @@ -1,3 +1,6 @@ +mcp: {} +api: {} + spec_version: 3.0 package: demo_tezos_auction @@ -40,10 +43,10 @@ templates: destination: entrypoint: withdraw -indexes: - tzcolors: - template: auction - values: - datasource: tzkt - minter: tzcolors_minter - auction: tzcolors_auction \ No newline at end of file +# indexes: +# tzcolors: +# template: auction +# values: +# datasource: tzkt +# minter: tzcolors_minter +# auction: tzcolors_auction \ No newline at end of file diff --git a/src/dipdup/api.py b/src/dipdup/api.py index 95fe5d5c2..3dd2a5c7b 100644 --- a/src/dipdup/api.py +++ b/src/dipdup/api.py @@ -2,6 +2,7 @@ from collections.abc import Awaitable from collections.abc import Callable from json import JSONDecodeError +from typing import TYPE_CHECKING import orjson from aiohttp import web @@ -12,6 +13,9 @@ from dipdup.utils import json_dumps +if TYPE_CHECKING: + from mcp.server.fastmcp import FastMCP + def _method_wrapper( ctx: 'DipDupContext', method: Callable[[DipDupContext, web.Request], Awaitable[web.Response]], @@ -48,6 +52,32 @@ async def _performance(ctx: 'DipDupContext', request: web.Request) -> web.Respon dumps=lambda x: json_dumps(x, option=orjson.OPT_SORT_KEYS).decode(), ) +async def create_mcp() -> 'FastMCP': + from mcp.server.fastmcp import FastMCP + from dipdup import models + + mcp = FastMCP('DipDup', port=9999) + + @mcp.tool(name='Indexes', description='Fetch the current state of the indexer') + async def indexes() -> str: + index = await models.Index.filter().limit(1).get() + return str(index.__dict__) + + + @mcp.tool(name='Head', description='Fetch the current head block') + async def head() -> str: + res = '' + for m in (await models.Head.all()): + res += f""" +Datasource name: {m.name} +Current height: {m.level} +Block hash: {m.hash} +Block timestamp: {m.timestamp} +""" + + return res + + return mcp async def create_api(ctx: DipDupContext) -> web.Application: routes = web.RouteTableDef() diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 9e210da44..9e917be86 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -3,6 +3,7 @@ import atexit import logging import sys +from threading import Thread import traceback from collections import defaultdict from collections.abc import Callable @@ -529,6 +530,35 @@ async def hasura(ctx: click.Context) -> None: pass +@cli.group(help='Commands related to MCP integration.') +@click.pass_context +@_cli_wrapper +async def mcp(ctx: click.Context) -> None: + pass + + +@mcp.command(name='run') +@click.pass_context +@_cli_wrapper +async def mcp_run(ctx: click.Context) -> None: + """Run MCP server.""" + + from dipdup.api import create_mcp + + from anyio import run, from_thread + + mcp = await create_mcp() + + + # async def main(): + with from_thread.start_blocking_portal() as portal: + portal.call(mcp.run_sse_async) + + # thread = Thread(target=mcp.run) + # thread.run() + # config: DipDupConfig = ctx.obj.config + # run_mcp(config) + @hasura.command(name='configure') @click.option('--force', '-f', is_flag=True, help='Proceed even if Hasura is already configured.') @click.pass_context diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 48a5a34b7..64f98780b 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -545,6 +545,18 @@ class ApiConfig: port: int = 46339 # dial INDEX 😎 +@dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) +class McpConfig: + """Config for MCP integration. + + :param host: Host to bind to + :param port: Port to bind to + """ + + host: str = '127.0.0.1' + port: int = 9999 + + # NOTE: Should be the only place where extras are allowed @dataclass(config=ConfigDict(extra='allow', defer_build=True), kw_only=True) class AdvancedConfig: @@ -610,6 +622,7 @@ class DipDupConfig(InteractiveMixin): advanced: AdvancedConfig = Field(default_factory=AdvancedConfig) custom: dict[str, Any] = Field(default_factory=dict) logging: dict[str, str | int] | str | int = 'INFO' + mcp: McpConfig | None = None def __post_init__(self) -> None: if self.package != pascal_to_snake(self.package): diff --git a/src/dipdup/dipdup.py b/src/dipdup/dipdup.py index 01ea78a9c..e5ce8d0c5 100644 --- a/src/dipdup/dipdup.py +++ b/src/dipdup/dipdup.py @@ -1,5 +1,6 @@ import asyncio import logging +from threading import Thread import time from asyncio import CancelledError from asyncio import Event @@ -715,6 +716,7 @@ async def run(self) -> None: await self._set_up_hooks() await self._set_up_prometheus() await self._set_up_api(stack) + # await self._set_up_mcp(tasks) await self._initialize_schema() await self._initialize_migrations() diff --git a/uv.lock b/uv.lock index 4372a6db0..52faccc29 100644 --- a/uv.lock +++ b/uv.lock @@ -490,6 +490,7 @@ dependencies = [ { name = "datamodel-code-generator" }, { name = "eth-abi" }, { name = "lru-dict" }, + { name = "mcp" }, { name = "orjson" }, { name = "prometheus-client" }, { name = "pycryptodome" }, @@ -556,6 +557,7 @@ requires-dist = [ { name = "datamodel-code-generator", specifier = "~=0.26" }, { name = "eth-abi", specifier = "~=5.0" }, { name = "lru-dict", specifier = "~=1.3" }, + { name = "mcp", specifier = ">=1.2.1" }, { name = "orjson", specifier = "~=3.10" }, { name = "prometheus-client", specifier = "~=0.20" }, { name = "pycryptodome", specifier = "~=3.20" }, @@ -795,6 +797,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/5c/e226de133afd8bb267ec27eead9ae3d784b95b39a287ed404caab39a5f50/genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7", size = 21470 }, ] +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + [[package]] name = "hexbytes" version = "1.3.0" @@ -804,6 +815,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/96/035871b535a728700d3cc5b94cf883706f345c5a088253f26f0bee0b7939/hexbytes-1.3.0-py3-none-any.whl", hash = "sha256:83720b529c6e15ed21627962938dc2dec9bb1010f17bbbd66bf1e6a8287d522c", size = 4902 }, ] +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + [[package]] name = "idna" version = "3.10" @@ -968,6 +1016,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/81/3ef15337c19d3e3432945aad738081a5f54c16885277c7dff300b5f85b24/marshmallow_oneofschema-3.1.1-py3-none-any.whl", hash = "sha256:ff4cb2a488785ee8edd521a765682c2c80c78b9dc48894124531bdfa1ec9303b", size = 5726 }, ] +[[package]] +name = "mcp" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/30/51e4555826126e3954fa2ab1e934bf74163c5fe05e98f38ca4d0f8abbf63/mcp-1.2.1.tar.gz", hash = "sha256:c9d43dbfe943aa1530e2be8f54b73af3ebfb071243827b4483d421684806cb45", size = 103968 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/0d/6770742a84c8aa1d36c0d628896a380584c5759612e66af7446af07d8775/mcp-1.2.1-py3-none-any.whl", hash = "sha256:579bf9c9157850ebb1344f3ca6f7a3021b0123c44c9f089ef577a7062522f0fd", size = 66453 }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1262,8 +1329,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/1b/d0b013bf7d1af7cf0a6a4fce13f5fe5813ab225313755367b36e714a63f8/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93", size = 2254397 }, { url = "https://files.pythonhosted.org/packages/14/71/4cbd3870d3e926c34706f705d6793159ac49d9a213e3ababcdade5864663/pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764", size = 1775641 }, { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, - { url = "https://files.pythonhosted.org/packages/25/b3/09ff7072e6d96c9939c24cf51d3c389d7c345bf675420355c22402f71b68/pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca", size = 1691593 }, - { url = "https://files.pythonhosted.org/packages/a8/91/38e43628148f68ba9b68dedbc323cf409e537fd11264031961fd7c744034/pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd", size = 1765997 }, ] [[package]] @@ -1305,6 +1370,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, ] +[[package]] +name = "pydantic-settings" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, +] + [[package]] name = "pygments" version = "2.19.1" @@ -1788,6 +1866,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, ] +[[package]] +name = "sse-starlette" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, +] + [[package]] name = "starknet-py" version = "0.25.0" @@ -1807,6 +1898,18 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/36/ea/2ec249b38dccd8391b714a0dce2172d6298d7e043b063e529d897622f88d/starknet_py-0.25.0.tar.gz", hash = "sha256:452f2c9ed5e235540209ec2042d22dcf79c6cec2a6850f5b518d61b3fada3706", size = 97660 } +[[package]] +name = "starlette" +version = "0.45.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, +] + [[package]] name = "strict-rfc3339" version = "0.7" @@ -1961,6 +2064,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, ] +[[package]] +name = "uvicorn" +version = "0.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, +] + [[package]] name = "uvloop" version = "0.21.0" From 18205c91e9ea0f6bb153fee5adc8f589e9c47b3f Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Feb 2025 08:41:11 -0300 Subject: [PATCH 13/30] wip --- src/dipdup/api.py | 23 ++++++++++++++++++----- src/dipdup/cli.py | 16 +++++++--------- src/dipdup/dipdup.py | 1 - 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/dipdup/api.py b/src/dipdup/api.py index 3dd2a5c7b..6c1c2d7e7 100644 --- a/src/dipdup/api.py +++ b/src/dipdup/api.py @@ -8,14 +8,15 @@ from aiohttp import web import dipdup.performance +from dipdup.config import McpConfig from dipdup.context import DipDupContext from dipdup.exceptions import Error from dipdup.utils import json_dumps - if TYPE_CHECKING: from mcp.server.fastmcp import FastMCP + def _method_wrapper( ctx: 'DipDupContext', method: Callable[[DipDupContext, web.Request], Awaitable[web.Response]], @@ -52,22 +53,33 @@ async def _performance(ctx: 'DipDupContext', request: web.Request) -> web.Respon dumps=lambda x: json_dumps(x, option=orjson.OPT_SORT_KEYS).decode(), ) -async def create_mcp() -> 'FastMCP': + +async def create_mcp(config: McpConfig) -> 'FastMCP': + import mcp.server.fastmcp.utilities.logging + + mcp.server.fastmcp.utilities.logging.configure_logging = lambda _: None + from mcp.server.fastmcp import FastMCP + from dipdup import models - mcp = FastMCP('DipDup', port=9999) + mcp = FastMCP( + 'DipDup', + host=config.host, + port=config.port, + debug=True, + log_level='DEBUG', + ) @mcp.tool(name='Indexes', description='Fetch the current state of the indexer') async def indexes() -> str: index = await models.Index.filter().limit(1).get() return str(index.__dict__) - @mcp.tool(name='Head', description='Fetch the current head block') async def head() -> str: res = '' - for m in (await models.Head.all()): + for m in await models.Head.all(): res += f""" Datasource name: {m.name} Current height: {m.level} @@ -79,6 +91,7 @@ async def head() -> str: return mcp + async def create_api(ctx: DipDupContext) -> web.Application: routes = web.RouteTableDef() routes.get('/')(_method_wrapper(ctx, _performance)) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 9e917be86..3d9a0f778 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -3,7 +3,6 @@ import atexit import logging import sys -from threading import Thread import traceback from collections import defaultdict from collections.abc import Callable @@ -543,21 +542,20 @@ async def mcp(ctx: click.Context) -> None: async def mcp_run(ctx: click.Context) -> None: """Run MCP server.""" - from dipdup.api import create_mcp + from anyio import from_thread - from anyio import run, from_thread + from dipdup.api import create_mcp + from dipdup.exceptions import ConfigurationError - mcp = await create_mcp() + config: DipDupConfig = ctx.obj.config + if not config.mcp: + raise ConfigurationError('`mcp` config section is empty') + mcp = await create_mcp(config.mcp) - # async def main(): with from_thread.start_blocking_portal() as portal: portal.call(mcp.run_sse_async) - # thread = Thread(target=mcp.run) - # thread.run() - # config: DipDupConfig = ctx.obj.config - # run_mcp(config) @hasura.command(name='configure') @click.option('--force', '-f', is_flag=True, help='Proceed even if Hasura is already configured.') diff --git a/src/dipdup/dipdup.py b/src/dipdup/dipdup.py index e5ce8d0c5..768156b0c 100644 --- a/src/dipdup/dipdup.py +++ b/src/dipdup/dipdup.py @@ -1,6 +1,5 @@ import asyncio import logging -from threading import Thread import time from asyncio import CancelledError from asyncio import Event From 09f06b85ece2b0cdf91537d6f82656f19bd69d7c Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Feb 2025 10:55:10 -0300 Subject: [PATCH 14/30] it's working --- src/demo_evm_events/dipdup.yaml | 4 +++- src/dipdup/api.py | 13 ++++++++++--- src/dipdup/cli.py | 10 +++++++++- src/dipdup/dipdup.py | 1 - 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/demo_evm_events/dipdup.yaml b/src/demo_evm_events/dipdup.yaml index d14101c55..cedf4f9c9 100644 --- a/src/demo_evm_events/dipdup.yaml +++ b/src/demo_evm_events/dipdup.yaml @@ -30,4 +30,6 @@ indexes: handlers: - callback: on_transfer contract: eth_usdt - name: Transfer \ No newline at end of file + name: Transfer + +mcp: {} \ No newline at end of file diff --git a/src/dipdup/api.py b/src/dipdup/api.py index 6c1c2d7e7..e3fd0259c 100644 --- a/src/dipdup/api.py +++ b/src/dipdup/api.py @@ -73,10 +73,17 @@ async def create_mcp(config: McpConfig) -> 'FastMCP': @mcp.tool(name='Indexes', description='Fetch the current state of the indexer') async def indexes() -> str: - index = await models.Index.filter().limit(1).get() - return str(index.__dict__) + res = '' + for m in await models.Index.all(): + res += f""" +Index name: {m.name} +Type: {m.type} +Status: {m.status} +Current height: {m.level} +""" + return res - @mcp.tool(name='Head', description='Fetch the current head block') + @mcp.tool(name='Heads', description='Fetch the current datasource head blocks') async def head() -> str: res = '' for m in await models.Head.all(): diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 3d9a0f778..b573edc42 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -545,6 +545,8 @@ async def mcp_run(ctx: click.Context) -> None: from anyio import from_thread from dipdup.api import create_mcp + from dipdup.config import DipDupConfig + from dipdup.database import tortoise_wrapper from dipdup.exceptions import ConfigurationError config: DipDupConfig = ctx.obj.config @@ -554,7 +556,13 @@ async def mcp_run(ctx: click.Context) -> None: mcp = await create_mcp(config.mcp) with from_thread.start_blocking_portal() as portal: - portal.call(mcp.run_sse_async) + async with tortoise_wrapper( + url=config.database.connection_string, + models=config.package, + timeout=config.database.connection_timeout, + ): + # await portal.spawn(mcp.run) + portal.call(mcp.run_sse_async) @hasura.command(name='configure') diff --git a/src/dipdup/dipdup.py b/src/dipdup/dipdup.py index 768156b0c..01ea78a9c 100644 --- a/src/dipdup/dipdup.py +++ b/src/dipdup/dipdup.py @@ -715,7 +715,6 @@ async def run(self) -> None: await self._set_up_hooks() await self._set_up_prometheus() await self._set_up_api(stack) - # await self._set_up_mcp(tasks) await self._initialize_schema() await self._initialize_migrations() From 909a92215ea006ca6a0027793da99448f606fff9 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Feb 2025 11:26:24 -0300 Subject: [PATCH 15/30] ref --- src/dipdup/api.py | 17 +++++++++++++---- src/dipdup/cli.py | 19 +++++++------------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/dipdup/api.py b/src/dipdup/api.py index e3fd0259c..8b70989e3 100644 --- a/src/dipdup/api.py +++ b/src/dipdup/api.py @@ -8,8 +8,8 @@ from aiohttp import web import dipdup.performance -from dipdup.config import McpConfig from dipdup.context import DipDupContext +from dipdup.exceptions import ConfigurationError from dipdup.exceptions import Error from dipdup.utils import json_dumps @@ -54,7 +54,7 @@ async def _performance(ctx: 'DipDupContext', request: web.Request) -> web.Respon ) -async def create_mcp(config: McpConfig) -> 'FastMCP': +async def create_mcp(ctx: DipDupContext) -> 'FastMCP': import mcp.server.fastmcp.utilities.logging mcp.server.fastmcp.utilities.logging.configure_logging = lambda _: None @@ -63,14 +63,23 @@ async def create_mcp(config: McpConfig) -> 'FastMCP': from dipdup import models + if not (mcp_config := ctx.config.mcp): + raise ConfigurationError('MCP config is not provided') + mcp = FastMCP( 'DipDup', - host=config.host, - port=config.port, + host=mcp_config.host, + port=mcp_config.port, + # FIXME: both not working debug=True, log_level='DEBUG', ) + @mcp.tool(name='Config', description='Describe the current configuration') + async def config() -> str: + # FIXME: strip secrets + return ctx.config.dump() + @mcp.tool(name='Indexes', description='Fetch the current state of the indexer') async def indexes() -> str: res = '' diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index b573edc42..2f60be18e 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -546,22 +546,17 @@ async def mcp_run(ctx: click.Context) -> None: from dipdup.api import create_mcp from dipdup.config import DipDupConfig - from dipdup.database import tortoise_wrapper - from dipdup.exceptions import ConfigurationError + from dipdup.dipdup import DipDup config: DipDupConfig = ctx.obj.config - if not config.mcp: - raise ConfigurationError('`mcp` config section is empty') - - mcp = await create_mcp(config.mcp) + dipdup = DipDup(config) + mcp = await create_mcp(dipdup._ctx) with from_thread.start_blocking_portal() as portal: - async with tortoise_wrapper( - url=config.database.connection_string, - models=config.package, - timeout=config.database.connection_timeout, - ): - # await portal.spawn(mcp.run) + async with AsyncExitStack() as stack: + await dipdup._create_datasources() + await dipdup._set_up_database(stack) + portal.call(mcp.run_sse_async) From ed51b18661de32a534c7c23df71ad4f7f475f8d9 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Feb 2025 13:53:54 -0300 Subject: [PATCH 16/30] ref, user example --- src/demo_evm_events/__init__.py | 12 +++++ src/dipdup/api.py | 59 --------------------- src/dipdup/cli.py | 11 +++- src/dipdup/mcp.py | 93 +++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 61 deletions(-) create mode 100644 src/dipdup/mcp.py diff --git a/src/demo_evm_events/__init__.py b/src/demo_evm_events/__init__.py index e69de29bb..905c01a96 100644 --- a/src/demo_evm_events/__init__.py +++ b/src/demo_evm_events/__init__.py @@ -0,0 +1,12 @@ +from dipdup import mcp + +from demo_evm_events import models + + +@mcp.tool('TopHolders', 'Top holders stats') +async def tool_holders() -> str: + holders = await models.Holder.filter().order_by('-balance').limit(10).all() + res = 'Top holders:\n' + for holder in holders: + res += f'{holder.address}: {holder.balance}\n' + return res diff --git a/src/dipdup/api.py b/src/dipdup/api.py index 8b70989e3..95fe5d5c2 100644 --- a/src/dipdup/api.py +++ b/src/dipdup/api.py @@ -2,20 +2,15 @@ from collections.abc import Awaitable from collections.abc import Callable from json import JSONDecodeError -from typing import TYPE_CHECKING import orjson from aiohttp import web import dipdup.performance from dipdup.context import DipDupContext -from dipdup.exceptions import ConfigurationError from dipdup.exceptions import Error from dipdup.utils import json_dumps -if TYPE_CHECKING: - from mcp.server.fastmcp import FastMCP - def _method_wrapper( ctx: 'DipDupContext', @@ -54,60 +49,6 @@ async def _performance(ctx: 'DipDupContext', request: web.Request) -> web.Respon ) -async def create_mcp(ctx: DipDupContext) -> 'FastMCP': - import mcp.server.fastmcp.utilities.logging - - mcp.server.fastmcp.utilities.logging.configure_logging = lambda _: None - - from mcp.server.fastmcp import FastMCP - - from dipdup import models - - if not (mcp_config := ctx.config.mcp): - raise ConfigurationError('MCP config is not provided') - - mcp = FastMCP( - 'DipDup', - host=mcp_config.host, - port=mcp_config.port, - # FIXME: both not working - debug=True, - log_level='DEBUG', - ) - - @mcp.tool(name='Config', description='Describe the current configuration') - async def config() -> str: - # FIXME: strip secrets - return ctx.config.dump() - - @mcp.tool(name='Indexes', description='Fetch the current state of the indexer') - async def indexes() -> str: - res = '' - for m in await models.Index.all(): - res += f""" -Index name: {m.name} -Type: {m.type} -Status: {m.status} -Current height: {m.level} -""" - return res - - @mcp.tool(name='Heads', description='Fetch the current datasource head blocks') - async def head() -> str: - res = '' - for m in await models.Head.all(): - res += f""" -Datasource name: {m.name} -Current height: {m.level} -Block hash: {m.hash} -Block timestamp: {m.timestamp} -""" - - return res - - return mcp - - async def create_api(ctx: DipDupContext) -> web.Application: routes = web.RouteTableDef() routes.get('/')(_method_wrapper(ctx, _performance)) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 2f60be18e..77e6f933c 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -214,6 +214,7 @@ def _skip_cli_group() -> bool: ['hasura'], ['package'], ['schema'], + ['mcp'], ) # NOTE: Simple helpers that don't use any of our cli boilerplate is_script_group = args[0] in ( @@ -544,13 +545,19 @@ async def mcp_run(ctx: click.Context) -> None: from anyio import from_thread - from dipdup.api import create_mcp from dipdup.config import DipDupConfig from dipdup.dipdup import DipDup + from dipdup.mcp import configure_mcp + from dipdup.mcp import get_mcp config: DipDupConfig = ctx.obj.config dipdup = DipDup(config) - mcp = await create_mcp(dipdup._ctx) + + mcp = get_mcp() + configure_mcp(dipdup._ctx) + + # NOTE: Import all submodules to find @mcp.tool decorators + dipdup._ctx.package.verify() with from_thread.start_blocking_portal() as portal: async with AsyncExitStack() as stack: diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py new file mode 100644 index 000000000..8839d976c --- /dev/null +++ b/src/dipdup/mcp.py @@ -0,0 +1,93 @@ +from typing import TYPE_CHECKING, Callable + +from dipdup import models +from dipdup.context import DipDupContext + +if TYPE_CHECKING: + from mcp.server.fastmcp import FastMCP + +_mcp = None +_ctx = None + + +async def _tool_config() -> str: + # FIXME: strip secrets + return _ctx.config.dump() + + +async def _tool_indexes() -> str: + res = '' + for m in await models.Index.all(): + res += f""" +Index name: {m.name} +Type: {m.type} +Status: {m.status} +Current height: {m.level} +""" + return res + + +async def _tool_heads() -> str: + res = '' + for m in await models.Head.all(): + res += f""" +Datasource name: {m.name} +Current height: {m.level} +Block hash: {m.hash} +Block timestamp: {m.timestamp} +""" + + return res + + +def _create_mcp() -> 'FastMCP': + from mcp.server.fastmcp import FastMCP + + mcp = FastMCP( + 'DipDup', + # FIXME: both not working + debug=True, + log_level='DEBUG', + ) + + # NOTE: Internal tools + + mcp.tool( + name='Config', + description='Describe the current configuration', + )(_tool_config) + + mcp.tool( + name='Indexes', + description='Fetch the current state of the indexer', + )(_tool_indexes) + + mcp.tool( + name='Heads', + description='Fetch the current datasource head blocks', + )(_tool_heads) + + return mcp + + +def configure_mcp(ctx: DipDupContext) -> None: + global _ctx + _ctx = ctx + + if mcp_config := ctx.config.mcp: + mcp = get_mcp() + mcp.settings.host = mcp_config.host + mcp.settings.port = mcp_config.port + + +def get_mcp() -> 'FastMCP': + global _mcp + + if not _mcp: + _mcp = _create_mcp() + + return _mcp + + +def tool(name: str, description: str) -> Callable: + return get_mcp().tool(name=name, description=description) From 1119154e3e416e7a3e3b91d7e3c507b59bd15588 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Mon, 24 Feb 2025 11:46:45 -0300 Subject: [PATCH 17/30] templates --- src/dipdup/mcp.py | 11 +++++++---- .../projects/base/configs/dipdup.compose.yaml.j2 | 6 ++++++ .../projects/base/configs/dipdup.sqlite.yaml.j2 | 6 ++++++ src/dipdup/projects/base/configs/dipdup.swarm.yaml.j2 | 6 ++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index 8839d976c..b467a1d3c 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Callable +from collections.abc import Callable +from typing import TYPE_CHECKING from dipdup import models from dipdup.context import DipDupContext @@ -6,11 +7,12 @@ if TYPE_CHECKING: from mcp.server.fastmcp import FastMCP -_mcp = None -_ctx = None +_mcp: 'FastMCP | None' = None +_ctx: DipDupContext | None = None async def _tool_config() -> str: + assert _ctx # FIXME: strip secrets return _ctx.config.dump() @@ -89,5 +91,6 @@ def get_mcp() -> 'FastMCP': return _mcp -def tool(name: str, description: str) -> Callable: +def tool(name: str, description: str) -> Callable[..., None]: + assert ' ' not in name, 'Tool name should not contain spaces' return get_mcp().tool(name=name, description=description) diff --git a/src/dipdup/projects/base/configs/dipdup.compose.yaml.j2 b/src/dipdup/projects/base/configs/dipdup.compose.yaml.j2 index c17df5459..6cf9afed6 100644 --- a/src/dipdup/projects/base/configs/dipdup.compose.yaml.j2 +++ b/src/dipdup/projects/base/configs/dipdup.compose.yaml.j2 @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 diff --git a/src/dipdup/projects/base/configs/dipdup.sqlite.yaml.j2 b/src/dipdup/projects/base/configs/dipdup.sqlite.yaml.j2 index cfacbfb9a..cb24fa0e2 100644 --- a/src/dipdup/projects/base/configs/dipdup.sqlite.yaml.j2 +++ b/src/dipdup/projects/base/configs/dipdup.sqlite.yaml.j2 @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 diff --git a/src/dipdup/projects/base/configs/dipdup.swarm.yaml.j2 b/src/dipdup/projects/base/configs/dipdup.swarm.yaml.j2 index 39eb329fb..a2b387838 100644 --- a/src/dipdup/projects/base/configs/dipdup.swarm.yaml.j2 +++ b/src/dipdup/projects/base/configs/dipdup.swarm.yaml.j2 @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 From dd578233e4599737dffcafe27a2367081506c800 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Mon, 17 Mar 2025 09:21:51 -0300 Subject: [PATCH 18/30] WIP --- docs/7.references/1.cli.md | 18 + docs/7.references/2.config.md | 129 +++---- docs/7.references/3.context.md | 26 +- docs/7.references/4.models.md | 98 +++--- docs/9.release-notes/1.v8.3.md | 13 + .../9.release-notes/{9.v7.0.md => 10.v7.0.md} | 0 docs/9.release-notes/{1.v8.2.md => 2.v8.2.md} | 0 docs/9.release-notes/{2.v8.1.md => 3.v8.1.md} | 0 docs/9.release-notes/{3.v8.0.md => 4.v8.0.md} | 0 docs/9.release-notes/{4.v7.5.md => 5.v7.5.md} | 0 docs/9.release-notes/{5.v7.4.md => 6.v7.4.md} | 0 docs/9.release-notes/{6.v7.3.md => 7.v7.3.md} | 0 docs/9.release-notes/{7.v7.2.md => 8.v7.2.md} | 0 docs/9.release-notes/{8.v7.1.md => 9.v7.1.md} | 0 docs/9.release-notes/_8.3_changelog.md | 0 indexer_state.py | 20 -- requirements.txt | 262 +++++++------- .../handlers/factory/pool_created.py | 7 +- src/demo_evm_uniswap/models/abi.py | 2 +- src/demo_evm_uniswap/models/repo.py | 7 +- .../handlers/on_update_expiry_map.py | 2 +- .../hooks/check_expiration.py | 7 +- .../handlers/on_factory_origination.py | 4 +- src/dipdup/_version.py | 4 +- src/dipdup/cli.py | 4 +- src/dipdup/codegen/evm.py | 2 +- src/dipdup/codegen/starknet.py | 2 +- src/dipdup/codegen/substrate.py | 2 +- src/dipdup/codegen/tezos.py | 8 +- src/dipdup/config/__init__.py | 4 +- src/dipdup/config/_mixin.py | 2 +- src/dipdup/config/substrate_events.py | 2 +- src/dipdup/config/tezos_operations.py | 4 +- src/dipdup/database.py | 2 +- src/dipdup/datasources/_subsquid.py | 2 +- src/dipdup/datasources/coinbase.py | 2 +- src/dipdup/datasources/evm_etherscan.py | 4 +- src/dipdup/datasources/substrate_subscan.py | 4 +- src/dipdup/datasources/tezos_tzkt.py | 22 +- src/dipdup/datasources/tzip_metadata.py | 4 +- src/dipdup/hasura.py | 6 +- src/dipdup/index.py | 2 +- src/dipdup/indexes/tezos_operations/parser.py | 4 +- src/dipdup/install.py | 2 +- src/dipdup/mcp.py | 137 ++++++-- src/dipdup/models/__init__.py | 7 +- src/dipdup/models/substrate.py | 2 +- src/dipdup/package.py | 2 +- src/dipdup/performance.py | 4 +- tests/test_index/test_tzkt_operations.py | 4 +- uv.lock | 319 +++++++++--------- 51 files changed, 640 insertions(+), 518 deletions(-) create mode 100644 docs/9.release-notes/1.v8.3.md rename docs/9.release-notes/{9.v7.0.md => 10.v7.0.md} (100%) rename docs/9.release-notes/{1.v8.2.md => 2.v8.2.md} (100%) rename docs/9.release-notes/{2.v8.1.md => 3.v8.1.md} (100%) rename docs/9.release-notes/{3.v8.0.md => 4.v8.0.md} (100%) rename docs/9.release-notes/{4.v7.5.md => 5.v7.5.md} (100%) rename docs/9.release-notes/{5.v7.4.md => 6.v7.4.md} (100%) rename docs/9.release-notes/{6.v7.3.md => 7.v7.3.md} (100%) rename docs/9.release-notes/{7.v7.2.md => 8.v7.2.md} (100%) rename docs/9.release-notes/{8.v7.1.md => 9.v7.1.md} (100%) create mode 100644 docs/9.release-notes/_8.3_changelog.md delete mode 100644 indexer_state.py diff --git a/docs/7.references/1.cli.md b/docs/7.references/1.cli.md index fb320104b..d2a29d5af 100644 --- a/docs/7.references/1.cli.md +++ b/docs/7.references/1.cli.md @@ -203,6 +203,24 @@ Discord:
dipdup mcp [OPTIONS] COMMAND [ARGS]...
+
+ + + +### run + +

Run MCP server.

+
dipdup mcp run [OPTIONS]
+
+
+ + + + ## migrate

Migrate project to the new spec version.

diff --git a/docs/7.references/2.config.md b/docs/7.references/2.config.md index e41959e14..e755ae1b9 100644 --- a/docs/7.references/2.config.md +++ b/docs/7.references/2.config.md @@ -10,7 +10,7 @@ description: "Config file reference" ## dipdup.config.DipDupConfig -class dipdup.config.DipDupConfig(*args, spec_version, package, datasources=<factory>, database=<factory>, runtimes=<factory>, contracts=<factory>, indexes=<factory>, templates=<factory>, jobs=<factory>, hooks=<factory>, hasura=None, sentry=None, prometheus=None, api=None, advanced=<factory>, custom=<factory>, logging='INFO') +class dipdup.config.DipDupConfig(*args, spec_version, package, datasources=<factory>, database=<factory>, runtimes=<factory>, contracts=<factory>, indexes=<factory>, templates=<factory>, jobs=<factory>, hooks=<factory>, hasura=None, sentry=None, prometheus=None, api=None, advanced=<factory>, custom=<factory>, logging='INFO', mcp=None)

DipDup project configuration file

Parameters:
@@ -33,6 +33,7 @@ description: "Config file reference"
  • custom (dict[str, Any]) – User-defined configuration to use in callbacks

  • logging (dict[str, str | int] | str | int) – Modify logging verbosity

  • +
  • mcp (McpConfig | None)

  • @@ -50,7 +51,7 @@ description: "Config file reference" ## dipdup.config.ContractConfig -class dipdup.config.ContractConfig(**kwargs) +class dipdup.config.ContractConfig(**kwargs)

    Contract config

    Parameters:
    @@ -68,7 +69,7 @@ description: "Config file reference" ## dipdup.config.AdvancedConfig -class dipdup.config.AdvancedConfig(**kwargs) +class dipdup.config.AdvancedConfig(**kwargs)

    This section allows users to tune some system-wide options, either experimental or unsuitable for generic configurations.

    Parameters:
    @@ -92,7 +93,7 @@ description: "Config file reference" ## dipdup.config.ApiConfig -class dipdup.config.ApiConfig(**kwargs) +class dipdup.config.ApiConfig(**kwargs)

    Management API config

    Parameters:
    @@ -110,7 +111,7 @@ description: "Config file reference" ## dipdup.config.coinbase.CoinbaseDatasourceConfig -class dipdup.config.coinbase.CoinbaseDatasourceConfig(*args) +class dipdup.config.coinbase.CoinbaseDatasourceConfig(*args)

    Coinbase datasource config

    Parameters:
    @@ -130,7 +131,7 @@ description: "Config file reference" ## dipdup.config.evm.EvmContractConfig -class dipdup.config.evm.EvmContractConfig(*args) +class dipdup.config.evm.EvmContractConfig(*args)

    EVM contract config

    Parameters:
    @@ -149,7 +150,7 @@ description: "Config file reference" ## dipdup.config.evm_node.EvmNodeDatasourceConfig -class dipdup.config.evm_node.EvmNodeDatasourceConfig(*args) +class dipdup.config.evm_node.EvmNodeDatasourceConfig(*args)

    EVM node datasource config

    Parameters:
    @@ -169,7 +170,7 @@ description: "Config file reference" ## dipdup.config.evm_etherscan.EvmEtherscanDatasourceConfig -class dipdup.config.evm_etherscan.EvmEtherscanDatasourceConfig(*args) +class dipdup.config.evm_etherscan.EvmEtherscanDatasourceConfig(*args)

    Etherscan datasource config

    Parameters:
    @@ -188,7 +189,7 @@ description: "Config file reference" ## dipdup.config.evm_events.EvmEventsHandlerConfig -class dipdup.config.evm_events.EvmEventsHandlerConfig(**kwargs) +class dipdup.config.evm_events.EvmEventsHandlerConfig(**kwargs)

    Subsquid event handler

    Parameters:
    @@ -207,7 +208,7 @@ description: "Config file reference" ## dipdup.config.evm_events.EvmEventsIndexConfig -class dipdup.config.evm_events.EvmEventsIndexConfig(*args) +class dipdup.config.evm_events.EvmEventsIndexConfig(*args)

    Subsquid datasource config

    Parameters:
    @@ -227,7 +228,7 @@ description: "Config file reference" ## dipdup.config.evm_subsquid.EvmSubsquidDatasourceConfig -class dipdup.config.evm_subsquid.EvmSubsquidDatasourceConfig(*args) +class dipdup.config.evm_subsquid.EvmSubsquidDatasourceConfig(*args)

    Subsquid datasource config

    Parameters:
    @@ -245,7 +246,7 @@ description: "Config file reference" ## dipdup.config.evm_transactions.EvmTransactionsHandlerConfig -class dipdup.config.evm_transactions.EvmTransactionsHandlerConfig(**kwargs) +class dipdup.config.evm_transactions.EvmTransactionsHandlerConfig(**kwargs)

    Subsquid transaction handler

    Parameters:
    @@ -266,7 +267,7 @@ description: "Config file reference" ## dipdup.config.evm_transactions.EvmTransactionsIndexConfig -class dipdup.config.evm_transactions.EvmTransactionsIndexConfig(*args) +class dipdup.config.evm_transactions.EvmTransactionsIndexConfig(*args)

    Index that uses Subsquid Network as a datasource for transactions

    Parameters:
    @@ -286,7 +287,7 @@ description: "Config file reference" ## dipdup.config.HandlerConfig -class dipdup.config.HandlerConfig(**kwargs) +class dipdup.config.HandlerConfig(**kwargs)

    Base class for index handlers

    Parameters:
    @@ -303,7 +304,7 @@ description: "Config file reference" ## dipdup.config.HasuraConfig -class dipdup.config.HasuraConfig(**kwargs) +class dipdup.config.HasuraConfig(**kwargs)

    Config for the Hasura integration.

    Parameters:
    @@ -331,7 +332,7 @@ description: "Config file reference" ## dipdup.config.HookConfig -class dipdup.config.HookConfig(**kwargs) +class dipdup.config.HookConfig(**kwargs)

    Hook config

    Parameters:
    @@ -349,7 +350,7 @@ description: "Config file reference" ## dipdup.config.HttpConfig -class dipdup.config.HttpConfig(retry_count=None, retry_sleep=None, retry_multiplier=None, ratelimit_rate=None, ratelimit_period=None, ratelimit_sleep=None, connection_limit=None, connection_timeout=None, request_timeout=None, batch_size=None, polling_interval=None, replay=None, replay_path=None, alias=None) +class dipdup.config.HttpConfig(retry_count=None, retry_sleep=None, retry_multiplier=None, ratelimit_rate=None, ratelimit_period=None, ratelimit_sleep=None, connection_limit=None, connection_timeout=None, request_timeout=None, batch_size=None, polling_interval=None, replay=None, replay_path=None, alias=None)

    Advanced configuration of HTTP client

    Parameters:
    @@ -378,7 +379,7 @@ description: "Config file reference" ## dipdup.config.http.HttpDatasourceConfig -class dipdup.config.http.HttpDatasourceConfig(*args) +class dipdup.config.http.HttpDatasourceConfig(*args)

    Generic HTTP datasource config

    Parameters:
    @@ -396,7 +397,7 @@ description: "Config file reference" ## dipdup.config.IndexConfig -class dipdup.config.IndexConfig(*args) +class dipdup.config.IndexConfig(*args)

    Index config

    Parameters:
    @@ -413,7 +414,7 @@ description: "Config file reference" ## dipdup.config.DatasourceConfig -class dipdup.config.DatasourceConfig(*args) +class dipdup.config.DatasourceConfig(*args)

    Base class for datasource configs

    Parameters:
    @@ -431,7 +432,7 @@ description: "Config file reference" ## dipdup.config.IndexTemplateConfig -class dipdup.config.IndexTemplateConfig(*args) +class dipdup.config.IndexTemplateConfig(*args)

    Index template config

    Parameters:
    @@ -451,7 +452,7 @@ description: "Config file reference" ## dipdup.config.ipfs.IpfsDatasourceConfig -class dipdup.config.ipfs.IpfsDatasourceConfig(*args) +class dipdup.config.ipfs.IpfsDatasourceConfig(*args)

    IPFS datasource config

    Parameters:
    @@ -469,7 +470,7 @@ description: "Config file reference" ## dipdup.config.JobConfig -class dipdup.config.JobConfig(*args) +class dipdup.config.JobConfig(*args)

    Job schedule config

    Parameters:
    @@ -488,7 +489,7 @@ description: "Config file reference" ## dipdup.config.PostgresDatabaseConfig -class dipdup.config.PostgresDatabaseConfig(**kwargs) +class dipdup.config.PostgresDatabaseConfig(**kwargs)

    Postgres database connection config

    Parameters:
    @@ -513,7 +514,7 @@ description: "Config file reference" ## dipdup.config.PrometheusConfig -class dipdup.config.PrometheusConfig(**kwargs) +class dipdup.config.PrometheusConfig(**kwargs)

    Config for Prometheus integration.

    Parameters:
    @@ -532,7 +533,7 @@ description: "Config file reference" ## dipdup.config.ResolvedHttpConfig -class dipdup.config.ResolvedHttpConfig(**kwargs) +class dipdup.config.ResolvedHttpConfig(**kwargs)

    Advanced configuration of HTTP client

    Parameters:
    @@ -562,7 +563,7 @@ description: "Config file reference" ## dipdup.config.RuntimeConfig -class dipdup.config.RuntimeConfig(*args) +class dipdup.config.RuntimeConfig(*args)

    Runtime config

    Parameters:
    @@ -579,7 +580,7 @@ description: "Config file reference" ## dipdup.config.SentryConfig -class dipdup.config.SentryConfig(**kwargs) +class dipdup.config.SentryConfig(**kwargs)

    Config for Sentry integration.

    Parameters:
    @@ -601,7 +602,7 @@ description: "Config file reference" ## dipdup.config.SqliteDatabaseConfig -class dipdup.config.SqliteDatabaseConfig(**kwargs) +class dipdup.config.SqliteDatabaseConfig(**kwargs)

    SQLite connection config

    Parameters:
    @@ -620,7 +621,7 @@ description: "Config file reference" ## dipdup.config.SystemHookConfig -class dipdup.config.SystemHookConfig(callback, atomic=False) +class dipdup.config.SystemHookConfig(callback, atomic=False)

    Hook config

    Parameters:
    @@ -637,7 +638,7 @@ description: "Config file reference" ## dipdup.config.tezos.TezosContractConfig -class dipdup.config.tezos.TezosContractConfig(*args) +class dipdup.config.tezos.TezosContractConfig(*args)

    Tezos contract config.

    Parameters:
    @@ -656,7 +657,7 @@ description: "Config file reference" ## dipdup.config.tezos.TezosIndexConfig -class dipdup.config.tezos.TezosIndexConfig(kind, datasources) +class dipdup.config.tezos.TezosIndexConfig(kind, datasources)

    TzKT index config

    Parameters:
    @@ -673,7 +674,7 @@ description: "Config file reference" ## dipdup.config.tezos_big_maps.TezosBigMapsHandlerConfig -class dipdup.config.tezos_big_maps.TezosBigMapsHandlerConfig(callback) +class dipdup.config.tezos_big_maps.TezosBigMapsHandlerConfig(callback)

    Big map handler config

    Parameters:
    @@ -691,7 +692,7 @@ description: "Config file reference" ## dipdup.config.tezos_big_maps.TezosBigMapsIndexConfig -class dipdup.config.tezos_big_maps.TezosBigMapsIndexConfig(kind, datasources) +class dipdup.config.tezos_big_maps.TezosBigMapsIndexConfig(kind, datasources)

    Big map index config

    Parameters:
    @@ -712,7 +713,7 @@ description: "Config file reference" ## dipdup.config.tezos_events.TezosEventsHandlerConfig -class dipdup.config.tezos_events.TezosEventsHandlerConfig(callback) +class dipdup.config.tezos_events.TezosEventsHandlerConfig(callback)

    Event handler config

    Parameters:
    @@ -730,7 +731,7 @@ description: "Config file reference" ## dipdup.config.tezos_events.TezosEventsIndexConfig -class dipdup.config.tezos_events.TezosEventsIndexConfig(kind, datasources) +class dipdup.config.tezos_events.TezosEventsIndexConfig(kind, datasources)

    Event index config

    Parameters:
    @@ -750,7 +751,7 @@ description: "Config file reference" ## dipdup.config.tezos_events.TezosEventsUnknownEventHandlerConfig -class dipdup.config.tezos_events.TezosEventsUnknownEventHandlerConfig(callback) +class dipdup.config.tezos_events.TezosEventsUnknownEventHandlerConfig(callback)

    Unknown event handler config

    Parameters:
    @@ -767,7 +768,7 @@ description: "Config file reference" ## dipdup.config.tezos_head.TezosTzktHeadHandlerConfig -class dipdup.config.tezos_head.TezosTzktHeadHandlerConfig(callback) +class dipdup.config.tezos_head.TezosTzktHeadHandlerConfig(callback)

    Head block handler config

    Parameters:
    @@ -783,7 +784,7 @@ description: "Config file reference" ## dipdup.config.tezos_head.TezosHeadIndexConfig -class dipdup.config.tezos_head.TezosHeadIndexConfig(kind, datasources) +class dipdup.config.tezos_head.TezosHeadIndexConfig(kind, datasources)

    Head block index config

    Parameters:
    @@ -801,7 +802,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsHandlerConfig -class dipdup.config.tezos_operations.TezosOperationsHandlerConfig(callback) +class dipdup.config.tezos_operations.TezosOperationsHandlerConfig(callback)

    Operation handler config

    Parameters:
    @@ -818,7 +819,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsHandlerOriginationPatternConfig -class dipdup.config.tezos_operations.TezosOperationsHandlerOriginationPatternConfig(**kwargs) +class dipdup.config.tezos_operations.TezosOperationsHandlerOriginationPatternConfig(**kwargs)

    Origination handler pattern config

    Parameters:
    @@ -840,7 +841,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsHandlerSmartRollupCementPatternConfig -class dipdup.config.tezos_operations.TezosOperationsHandlerSmartRollupCementPatternConfig(**kwargs) +class dipdup.config.tezos_operations.TezosOperationsHandlerSmartRollupCementPatternConfig(**kwargs)

    Operation handler pattern config

    Parameters:
    @@ -861,7 +862,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsHandlerSmartRollupExecutePatternConfig -class dipdup.config.tezos_operations.TezosOperationsHandlerSmartRollupExecutePatternConfig(**kwargs) +class dipdup.config.tezos_operations.TezosOperationsHandlerSmartRollupExecutePatternConfig(**kwargs)

    Operation handler pattern config

    Parameters:
    @@ -882,7 +883,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsHandlerTransactionPatternConfig -class dipdup.config.tezos_operations.TezosOperationsHandlerTransactionPatternConfig(**kwargs) +class dipdup.config.tezos_operations.TezosOperationsHandlerTransactionPatternConfig(**kwargs)

    Transaction handler pattern config

    Parameters:
    @@ -904,7 +905,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsIndexConfig -class dipdup.config.tezos_operations.TezosOperationsIndexConfig(kind, datasources) +class dipdup.config.tezos_operations.TezosOperationsIndexConfig(kind, datasources)

    Operation index config

    Parameters:
    @@ -926,7 +927,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsPatternConfig -class dipdup.config.tezos_operations.TezosOperationsPatternConfig(**kwargs) +class dipdup.config.tezos_operations.TezosOperationsPatternConfig(**kwargs)

    Base class for pattern config items.

    Contains methods for import and method signature generation during handler callbacks codegen.

    @@ -943,7 +944,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsUnfilteredHandlerConfig -class dipdup.config.tezos_operations.TezosOperationsUnfilteredHandlerConfig(callback) +class dipdup.config.tezos_operations.TezosOperationsUnfilteredHandlerConfig(callback)

    Handler of unfiltered operation index

    Parameters:
    @@ -959,7 +960,7 @@ description: "Config file reference" ## dipdup.config.tezos_operations.TezosOperationsUnfilteredIndexConfig -class dipdup.config.tezos_operations.TezosOperationsUnfilteredIndexConfig(kind, datasources) +class dipdup.config.tezos_operations.TezosOperationsUnfilteredIndexConfig(kind, datasources)

    Operation index config

    Parameters:
    @@ -980,7 +981,7 @@ description: "Config file reference" ## dipdup.config.tezos_tzkt.TezosTzktDatasourceConfig -class dipdup.config.tezos_tzkt.TezosTzktDatasourceConfig(*args) +class dipdup.config.tezos_tzkt.TezosTzktDatasourceConfig(*args)

    TzKT datasource config

    Parameters:
    @@ -1001,7 +1002,7 @@ description: "Config file reference" ## dipdup.config.tezos_token_balances.TezosTokenBalancesHandlerConfig -class dipdup.config.tezos_token_balances.TezosTokenBalancesHandlerConfig(callback) +class dipdup.config.tezos_token_balances.TezosTokenBalancesHandlerConfig(callback)

    Token balance handler config

    Parameters:
    @@ -1019,7 +1020,7 @@ description: "Config file reference" ## dipdup.config.tezos_token_balances.TezosTokenBalancesIndexConfig -class dipdup.config.tezos_token_balances.TezosTokenBalancesIndexConfig(kind, datasources) +class dipdup.config.tezos_token_balances.TezosTokenBalancesIndexConfig(kind, datasources)

    Token balance index config

    Parameters:
    @@ -1039,7 +1040,7 @@ description: "Config file reference" ## dipdup.config.tezos_token_transfers.TezosTokenTransfersHandlerConfig -class dipdup.config.tezos_token_transfers.TezosTokenTransfersHandlerConfig(callback) +class dipdup.config.tezos_token_transfers.TezosTokenTransfersHandlerConfig(callback)

    Token transfer handler config

    Parameters:
    @@ -1059,7 +1060,7 @@ description: "Config file reference" ## dipdup.config.tezos_token_transfers.TezosTokenTransfersIndexConfig -class dipdup.config.tezos_token_transfers.TezosTokenTransfersIndexConfig(kind, datasources) +class dipdup.config.tezos_token_transfers.TezosTokenTransfersIndexConfig(kind, datasources)

    Token transfer index config

    Parameters:
    @@ -1079,7 +1080,7 @@ description: "Config file reference" ## dipdup.config.starknet.StarknetContractConfig -class dipdup.config.starknet.StarknetContractConfig(*args) +class dipdup.config.starknet.StarknetContractConfig(*args)

    Starknet contract config

    Parameters:
    @@ -1098,7 +1099,7 @@ description: "Config file reference" ## dipdup.config.starknet_events.StarknetEventsHandlerConfig -class dipdup.config.starknet_events.StarknetEventsHandlerConfig(callback) +class dipdup.config.starknet_events.StarknetEventsHandlerConfig(callback)

    Subsquid event handler

    Parameters:
    @@ -1116,7 +1117,7 @@ description: "Config file reference" ## dipdup.config.starknet_events.StarknetEventsIndexConfig -class dipdup.config.starknet_events.StarknetEventsIndexConfig(kind, datasources) +class dipdup.config.starknet_events.StarknetEventsIndexConfig(kind, datasources)

    Starknet events index config

    Parameters:
    @@ -1136,7 +1137,7 @@ description: "Config file reference" ## dipdup.config.starknet_node.StarknetNodeDatasourceConfig -class dipdup.config.starknet_node.StarknetNodeDatasourceConfig(*args) +class dipdup.config.starknet_node.StarknetNodeDatasourceConfig(*args)

    Starknet node datasource config

    Parameters:
    @@ -1156,7 +1157,7 @@ description: "Config file reference" ## dipdup.config.starknet_subsquid.StarknetSubsquidDatasourceConfig -class dipdup.config.starknet_subsquid.StarknetSubsquidDatasourceConfig(*args) +class dipdup.config.starknet_subsquid.StarknetSubsquidDatasourceConfig(*args)

    Subsquid datasource config

    Parameters:
    @@ -1174,7 +1175,7 @@ description: "Config file reference" ## dipdup.config.substrate.SubstrateRuntimeConfig -class dipdup.config.substrate.SubstrateRuntimeConfig(*args) +class dipdup.config.substrate.SubstrateRuntimeConfig(*args)

    Substrate runtime config

    Parameters:
    @@ -1191,7 +1192,7 @@ description: "Config file reference" ## dipdup.config.substrate_events.SubstrateEventsHandlerConfig -class dipdup.config.substrate_events.SubstrateEventsHandlerConfig(callback) +class dipdup.config.substrate_events.SubstrateEventsHandlerConfig(callback)

    Subsquid event handler

    Parameters:
    @@ -1208,7 +1209,7 @@ description: "Config file reference" ## dipdup.config.substrate_events.SubstrateEventsIndexConfig -class dipdup.config.substrate_events.SubstrateEventsIndexConfig(kind, datasources) +class dipdup.config.substrate_events.SubstrateEventsIndexConfig(kind, datasources)

    Subsquid datasource config

    Parameters:
    @@ -1230,7 +1231,7 @@ description: "Config file reference" ## dipdup.config.substrate_subsquid.SubstrateSubsquidDatasourceConfig -class dipdup.config.substrate_subsquid.SubstrateSubsquidDatasourceConfig(*args) +class dipdup.config.substrate_subsquid.SubstrateSubsquidDatasourceConfig(*args)

    Subsquid datasource config

    Parameters:
    @@ -1248,7 +1249,7 @@ description: "Config file reference" ## dipdup.config.substrate_subscan.SubstrateSubscanDatasourceConfig -class dipdup.config.substrate_subscan.SubstrateSubscanDatasourceConfig(*args) +class dipdup.config.substrate_subscan.SubstrateSubscanDatasourceConfig(*args)

    Subscan datasource config

    Parameters:
    @@ -1267,7 +1268,7 @@ description: "Config file reference" ## dipdup.config.tzip_metadata.TzipMetadataDatasourceConfig -class dipdup.config.tzip_metadata.TzipMetadataDatasourceConfig(*args) +class dipdup.config.tzip_metadata.TzipMetadataDatasourceConfig(*args)

    DipDup Metadata datasource config

    Parameters:
    diff --git a/docs/7.references/3.context.md b/docs/7.references/3.context.md index eba8986bb..2f286b646 100644 --- a/docs/7.references/3.context.md +++ b/docs/7.references/3.context.md @@ -10,7 +10,7 @@ description: "Context reference" ## dipdup.context.DipDupContext -class dipdup.context.DipDupContext(config, package, datasources, transactions) +class dipdup.context.DipDupContext(config, package, datasources, transactions)

    Common execution context for handler and hook callbacks.

    Parameters:
    @@ -29,7 +29,7 @@ description: "Context reference" ## dipdup.context.HandlerContext -class dipdup.context.HandlerContext(config, package, datasources, transactions, logger, handler_config) +class dipdup.context.HandlerContext(config, package, datasources, transactions, logger, handler_config)

    Execution context of handler callbacks.

    Parameters:
    @@ -49,7 +49,7 @@ description: "Context reference" ## dipdup.context.HookContext -class dipdup.context.HookContext(config, package, datasources, transactions, logger, hook_config) +class dipdup.context.HookContext(config, package, datasources, transactions, logger, hook_config)

    Execution context of hook callbacks.

    Parameters:
    @@ -69,7 +69,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.add_contract -async DipDupContext.add_contract(kind, name, address=None, typename=None, code_hash=None) +async DipDupContext.add_contract(kind, name, address=None, typename=None, code_hash=None)

    Adds contract to the inventory.

    Parameters:
    @@ -91,7 +91,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.add_index -async DipDupContext.add_index(name, template, values, first_level=0, last_level=0, state=None) +async DipDupContext.add_index(name, template, values, first_level=0, last_level=0, state=None)

    Adds a new index from template.

    Parameters:
    @@ -114,7 +114,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.execute_sql -async DipDupContext.execute_sql(name, *args, **kwargs) +async DipDupContext.execute_sql(name, *args, **kwargs)

    Executes SQL script(s) with given name.

    If the name path is a directory, all .sql scripts within it will be executed in alphabetical order.

    @@ -135,7 +135,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.execute_sql_query -async DipDupContext.execute_sql_query(name, *args) +async DipDupContext.execute_sql_query(name, *args)

    Executes SQL query with given name included with the project

    Parameters:
    @@ -155,7 +155,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.fire_hook -async DipDupContext.fire_hook(name, wait=True, **kwargs) +async DipDupContext.fire_hook(name, wait=True, **kwargs)

    Fire hook with given name and arguments.

    Parameters:
    @@ -303,7 +303,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.reindex -async DipDupContext.reindex(reason=None, **context) +async DipDupContext.reindex(reason=None, **context)

    Drops the entire database and starts the indexing process from scratch.

    Parameters:
    @@ -322,7 +322,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.restart -async DipDupContext.restart() +async DipDupContext.restart()

    Restart process and continue indexing.

    Return type:
    @@ -335,7 +335,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.rollback -async DipDupContext.rollback(index, from_level, to_level) +async DipDupContext.rollback(index, from_level, to_level)

    Rollback index to a given level reverting all changes made since that level.

    Parameters:
    @@ -355,7 +355,7 @@ description: "Context reference" ## dipdup.context.DipDupContext.update_contract_metadata -async DipDupContext.update_contract_metadata(network, address, metadata) +async DipDupContext.update_contract_metadata(network, address, metadata)

    Inserts or updates corresponding rows in the internal dipdup_contract_metadata table to provide a generic metadata interface (see docs).

    @@ -376,7 +376,7 @@ to provide a generic metadata interface (see docs).

    ## dipdup.context.DipDupContext.update_token_metadata -async DipDupContext.update_token_metadata(network, address, token_id, metadata) +async DipDupContext.update_token_metadata(network, address, token_id, metadata)

    Inserts or updates corresponding rows in the internal dipdup_token_metadata table to provide a generic metadata interface (see docs).

    diff --git a/docs/7.references/4.models.md b/docs/7.references/4.models.md index 8a2e1e545..ea70d49e1 100644 --- a/docs/7.references/4.models.md +++ b/docs/7.references/4.models.md @@ -14,7 +14,7 @@ description: "Models reference" ## dipdup.models.CachedModel -class dipdup.models.CachedModel(**kwargs) +class dipdup.models.CachedModel(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -26,7 +26,7 @@ description: "Models reference" ## dipdup.models.Model -class dipdup.models.Model(**kwargs) +class dipdup.models.Model(**kwargs)

    Base class for DipDup project models

    Parameters:
    @@ -44,7 +44,7 @@ description: "Models reference" ## dipdup.models.ContractMetadata -class dipdup.models.ContractMetadata(**kwargs) +class dipdup.models.ContractMetadata(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -56,7 +56,7 @@ description: "Models reference" ## dipdup.models.Head -class dipdup.models.Head(**kwargs) +class dipdup.models.Head(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -68,7 +68,7 @@ description: "Models reference" ## dipdup.models.TokenMetadata -class dipdup.models.TokenMetadata(**kwargs) +class dipdup.models.TokenMetadata(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -80,7 +80,7 @@ description: "Models reference" ## dipdup.models.Contract -class dipdup.models.Contract(**kwargs) +class dipdup.models.Contract(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -92,7 +92,7 @@ description: "Models reference" ## dipdup.models.ContractKind -class dipdup.models.ContractKind(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.ContractKind(*values)

    Mapping for contract kind in

    @@ -100,7 +100,7 @@ description: "Models reference" ## dipdup.models.Index -class dipdup.models.Index(**kwargs) +class dipdup.models.Index(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -112,14 +112,14 @@ description: "Models reference" ## dipdup.models.IndexStatus -class dipdup.models.IndexStatus(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.IndexStatus(*values)
    ## dipdup.models.IndexType -class dipdup.models.IndexType(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.IndexType(*values)

    Enum for dipdup.models.Index

    @@ -127,7 +127,7 @@ description: "Models reference" ## dipdup.models.Schema -class dipdup.models.Schema(**kwargs) +class dipdup.models.Schema(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -139,7 +139,7 @@ description: "Models reference" ## dipdup.models.ReindexingAction -class dipdup.models.ReindexingAction(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.ReindexingAction(*values)

    Action that should be performed on reindexing

    Parameters:
    @@ -156,7 +156,7 @@ description: "Models reference" ## dipdup.models.ReindexingReason -class dipdup.models.ReindexingReason(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.ReindexingReason(*values)

    Reason that caused reindexing

    Parameters:
    @@ -175,7 +175,7 @@ description: "Models reference" ## dipdup.models.SkipHistory -class dipdup.models.SkipHistory(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.SkipHistory(*values)

    Whether to skip indexing big map history and use only current state

    Parameters:
    @@ -192,7 +192,7 @@ description: "Models reference" ## dipdup.models.Meta -class dipdup.models.Meta(**kwargs) +class dipdup.models.Meta(**kwargs)
    Parameters:

    kwargs (Any)

    @@ -204,7 +204,7 @@ description: "Models reference" ## dipdup.models.ModelUpdate -class dipdup.models.ModelUpdate(**kwargs) +class dipdup.models.ModelUpdate(**kwargs)

    Model update created within versioned transactions

    Parameters:
    @@ -217,7 +217,7 @@ description: "Models reference" ## dipdup.models.ModelUpdateAction -class dipdup.models.ModelUpdateAction(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.ModelUpdateAction(*values)

    Mapping for actions in model update

    @@ -234,7 +234,7 @@ description: "Models reference" ## dipdup.models.evm.EvmTransaction -class dipdup.models.evm.EvmTransaction(data: dipdup.models.evm.EvmTransactionData, input: InputT) +class dipdup.models.evm.EvmTransaction(data: dipdup.models.evm.EvmTransactionData, input: InputT)
    Parameters:
      @@ -249,7 +249,7 @@ description: "Models reference" ## dipdup.models.evm.EvmTransactionData -class dipdup.models.evm.EvmTransactionData(access_list: tuple[dict[str, dipdup.fields.Any], ...] | None, block_hash: str, chain_id: int | None, contract_address: str | None, cumulative_gas_used: int | None, effective_gas_price: int | None, from_: str, gas: int, gas_price: int, gas_used: int | None, hash: str, input: str, level: int, max_fee_per_gas: int | None, max_priority_fee_per_gas: int | None, nonce: int, r: str | None, s: str | None, status: int | None, timestamp: int, to: str | None, transaction_index: int | None, type: int | None, value: int | None, v: int | None, y_parity: bool | None) +class dipdup.models.evm.EvmTransactionData(access_list: tuple[dict[str, dipdup.fields.Any], ...] | None, block_hash: str, chain_id: int | None, contract_address: str | None, cumulative_gas_used: int | None, effective_gas_price: int | None, from_: str, gas: int, gas_price: int, gas_used: int | None, hash: str, input: str, level: int, max_fee_per_gas: int | None, max_priority_fee_per_gas: int | None, nonce: int, r: str | None, s: str | None, status: int | None, timestamp: int, to: str | None, transaction_index: int | None, type: int | None, value: int | None, v: int | None, y_parity: bool | None)
      Parameters:
        @@ -288,7 +288,7 @@ description: "Models reference" ## dipdup.models.evm.EvmEvent -class dipdup.models.evm.EvmEvent(data: dipdup.models.evm.EvmEventData, payload: PayloadT) +class dipdup.models.evm.EvmEvent(data: dipdup.models.evm.EvmEventData, payload: PayloadT)
        Parameters:
          @@ -303,7 +303,7 @@ description: "Models reference" ## dipdup.models.evm.EvmEventData -class dipdup.models.evm.EvmEventData(address: str, block_hash: str, data: str, level: int, log_index: int, removed: bool, timestamp: int, topics: tuple[str, ...], transaction_hash: str, transaction_index: int) +class dipdup.models.evm.EvmEventData(address: str, block_hash: str, data: str, level: int, log_index: int, removed: bool, timestamp: int, topics: tuple[str, ...], transaction_hash: str, transaction_index: int)
          Parameters:
            @@ -331,7 +331,7 @@ description: "Models reference" ## dipdup.models.starknet.StarknetEvent -class dipdup.models.starknet.StarknetEvent(data: dipdup.models.starknet.StarknetEventData, payload: PayloadT) +class dipdup.models.starknet.StarknetEvent(data: dipdup.models.starknet.StarknetEventData, payload: PayloadT)
            Parameters:
              @@ -346,7 +346,7 @@ description: "Models reference" ## dipdup.models.starknet.StarknetEventData -class dipdup.models.starknet.StarknetEventData(level: int, block_hash: str, transaction_index: int | None, transaction_hash: str, timestamp: int | None, from_address: str, keys: tuple[str, ...], data: tuple[str, ...]) +class dipdup.models.starknet.StarknetEventData(level: int, block_hash: str, transaction_index: int | None, transaction_hash: str, timestamp: int | None, from_address: str, keys: tuple[str, ...], data: tuple[str, ...])
              Parameters:
                @@ -367,7 +367,7 @@ description: "Models reference" ## dipdup.models.starknet.StarknetTransactionData -class dipdup.models.starknet.StarknetTransactionData(level: int, block_hash: str, transaction_index: int, transaction_hash: str, timestamp: int, contract_address: str | None, entry_point_selector: str | None, calldata: tuple[str, ...] | None, max_fee: str | None, version: str, signature: tuple[str, ...] | None, nonce: str | None, type: str, sender_address: str | None, class_hash: str | None, compiled_class_hash: str | None, contract_address_salt: str | None, constructor_calldata: tuple[str, ...] | None) +class dipdup.models.starknet.StarknetTransactionData(level: int, block_hash: str, transaction_index: int, transaction_hash: str, timestamp: int, contract_address: str | None, entry_point_selector: str | None, calldata: tuple[str, ...] | None, max_fee: str | None, version: str, signature: tuple[str, ...] | None, nonce: str | None, type: str, sender_address: str | None, class_hash: str | None, compiled_class_hash: str | None, contract_address_salt: str | None, constructor_calldata: tuple[str, ...] | None)
                Parameters:
                  @@ -403,7 +403,7 @@ description: "Models reference" ## dipdup.models.substrate.SubstrateEvent -class dipdup.models.substrate.SubstrateEvent(data: dipdup.models.substrate.SubstrateEventData, runtime: dipdup.runtimes.SubstrateRuntime) +class dipdup.models.substrate.SubstrateEvent(data: dipdup.models.substrate.SubstrateEventData, runtime: dipdup.runtimes.SubstrateRuntime)
                  Parameters:
                    @@ -418,7 +418,7 @@ description: "Models reference" ## dipdup.models.substrate.SubstrateEventData -class dipdup.models.substrate.SubstrateEventData(*, name: str, index: int, extrinsic_index: int, call_address: list[str] | None, args: list[dipdup.fields.Any] | None = None, decoded_args: dict[str, dipdup.fields.Any] | list[dipdup.fields.Any] | None = None, header: dipdup.models.substrate._BlockHeader, header_extra: dipdup.models.substrate._BlockHeaderExtra | None) +class dipdup.models.substrate.SubstrateEventData(*, name: str, index: int, extrinsic_index: int, call_address: list[str] | None, args: list[dipdup.fields.Any] | None = None, decoded_args: dict[str, dipdup.fields.Any] | list[dipdup.fields.Any] | None = None, header: dipdup.models.substrate._BlockHeader, header_extra: dipdup.models.substrate._BlockHeaderExtra | None)
                    Parameters:
                      @@ -439,7 +439,7 @@ description: "Models reference" ## dipdup.models.substrate.SubstrateHeadBlockData -class dipdup.models.substrate.SubstrateHeadBlockData +class dipdup.models.substrate.SubstrateHeadBlockData
                    @@ -451,7 +451,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosBigMapAction -class dipdup.models.tezos.TezosBigMapAction(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.tezos.TezosBigMapAction(*values)

                    Mapping for action in TzKT response

                  @@ -459,7 +459,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosBigMapData -class dipdup.models.tezos.TezosBigMapData(id, level, operation_id, timestamp, bigmap, contract_address, path, action, active, key=None, value=None) +class dipdup.models.tezos.TezosBigMapData(id, level, operation_id, timestamp, bigmap, contract_address, path, action, active, key=None, value=None)

                  Basic structure for big map diffs from TzKT response

                  Parameters:
                  @@ -484,7 +484,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosBigMapDiff -class dipdup.models.tezos.TezosBigMapDiff(action, data, key, value) +class dipdup.models.tezos.TezosBigMapDiff(action, data, key, value)

                  Wrapper for matched big map diff with typed data passed to the handler

                  Parameters:
                  @@ -502,7 +502,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosBlockData -class dipdup.models.tezos.TezosBlockData(level, hash, timestamp, proto, validations, deposit, reward, fees, nonce_revealed, priority=None, baker_address=None, baker_alias=None) +class dipdup.models.tezos.TezosBlockData(level, hash, timestamp, proto, validations, deposit, reward, fees, nonce_revealed, priority=None, baker_address=None, baker_alias=None)

                  Basic structure for blocks received from TzKT REST API

                  Parameters:
                  @@ -528,7 +528,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosEvent -class dipdup.models.tezos.TezosEvent(data, payload) +class dipdup.models.tezos.TezosEvent(data, payload)
                  Parameters:
                    @@ -543,7 +543,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosEventData -class dipdup.models.tezos.TezosEventData(id, level, timestamp, tag, payload, contract_address, contract_alias=None, contract_code_hash=None, transaction_id=None) +class dipdup.models.tezos.TezosEventData(id, level, timestamp, tag, payload, contract_address, contract_alias=None, contract_code_hash=None, transaction_id=None)

                    Basic structure for events received from TzKT REST API

                    Parameters:
                    @@ -566,7 +566,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosHeadBlockData -class dipdup.models.tezos.TezosHeadBlockData(chain, chain_id, cycle, level, hash, protocol, next_protocol, timestamp, voting_epoch, voting_period, known_level, last_sync, synced, quote_level, quote_btc, quote_eur, quote_usd, quote_cny, quote_jpy, quote_krw, quote_eth, quote_gbp) +class dipdup.models.tezos.TezosHeadBlockData(chain, chain_id, cycle, level, hash, protocol, next_protocol, timestamp, voting_epoch, voting_period, known_level, last_sync, synced, quote_level, quote_btc, quote_eur, quote_usd, quote_cny, quote_jpy, quote_krw, quote_eth, quote_gbp)

                    Basic structure for head block received from TzKT SignalR API

                    Parameters:
                    @@ -602,7 +602,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosOperationData -class dipdup.models.tezos.TezosOperationData(type, id, level, timestamp, hash, counter, sender_address, target_address, initiator_address, amount, status, has_internals, storage, diffs=<factory>, block=None, sender_alias=None, nonce=None, target_alias=None, initiator_alias=None, entrypoint=None, parameter_json=None, originated_contract_address=None, originated_contract_alias=None, originated_contract_type_hash=None, originated_contract_code_hash=None, originated_contract_tzips=None, delegate_address=None, delegate_alias=None, target_code_hash=None, sender_code_hash=None, commitment_json=<factory>) +class dipdup.models.tezos.TezosOperationData(type, id, level, timestamp, hash, counter, sender_address, target_address, initiator_address, amount, status, has_internals, storage, diffs=<factory>, block=None, sender_alias=None, nonce=None, target_alias=None, initiator_alias=None, entrypoint=None, parameter_json=None, originated_contract_address=None, originated_contract_alias=None, originated_contract_type_hash=None, originated_contract_code_hash=None, originated_contract_tzips=None, delegate_address=None, delegate_alias=None, target_code_hash=None, sender_code_hash=None, commitment_json=<factory>)

                    Basic structure for operations from TzKT response

                    Parameters:
                    @@ -647,7 +647,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosOperationType -class dipdup.models.tezos.TezosOperationType(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.tezos.TezosOperationType(*values)

                    Type of blockchain operation

                    Parameters:
                    @@ -666,7 +666,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosOrigination -class dipdup.models.tezos.TezosOrigination(data, storage) +class dipdup.models.tezos.TezosOrigination(data, storage)

                    Wrapper for matched origination with typed data passed to the handler

                    Parameters:
                    @@ -682,7 +682,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosQuoteData -class dipdup.models.tezos.TezosQuoteData(level, timestamp, btc, eur, usd, cny, jpy, krw, eth, gbp) +class dipdup.models.tezos.TezosQuoteData(level, timestamp, btc, eur, usd, cny, jpy, krw, eth, gbp)

                    Basic structure for quotes received from TzKT REST API

                    Parameters:
                    @@ -706,7 +706,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosSmartRollupCement -class dipdup.models.tezos.TezosSmartRollupCement(data, commitment) +class dipdup.models.tezos.TezosSmartRollupCement(data, commitment)

                    Wrapper for matched smart rollup cement to the handler

                    Parameters:
                    @@ -722,7 +722,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosSmartRollupCommitment -class dipdup.models.tezos.TezosSmartRollupCommitment(id, initiator_address, initiator_alias, inbox_level, state, hash, ticks, first_level, first_time) +class dipdup.models.tezos.TezosSmartRollupCommitment(id, initiator_address, initiator_alias, inbox_level, state, hash, ticks, first_level, first_time)
                    Parameters:
                      @@ -744,7 +744,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosSmartRollupExecute -class dipdup.models.tezos.TezosSmartRollupExecute(data, commitment) +class dipdup.models.tezos.TezosSmartRollupExecute(data, commitment)

                      Wrapper for matched smart rollup execute to the handler

                      Parameters:
                      @@ -760,7 +760,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosTokenBalanceData -class dipdup.models.tezos.TezosTokenBalanceData(id, transfers_count, first_level, first_time, last_level, last_time, account_address=None, account_alias=None, tzkt_token_id=None, contract_address=None, contract_alias=None, token_id=None, standard=None, metadata=None, balance=None, balance_value=None) +class dipdup.models.tezos.TezosTokenBalanceData(id, transfers_count, first_level, first_time, last_level, last_time, account_address=None, account_alias=None, tzkt_token_id=None, contract_address=None, contract_alias=None, token_id=None, standard=None, metadata=None, balance=None, balance_value=None)

                      Basic structure for token transver received from TzKT SignalR API

                      Parameters:
                      @@ -790,14 +790,14 @@ description: "Models reference" ## dipdup.models.tezos.TezosTokenStandard -class dipdup.models.tezos.TezosTokenStandard(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.tezos.TezosTokenStandard(*values)
                      ## dipdup.models.tezos.TezosTokenTransferData -class dipdup.models.tezos.TezosTokenTransferData(id, level, timestamp, tzkt_token_id, contract_address=None, contract_alias=None, token_id=None, standard=None, metadata=None, from_alias=None, from_address=None, to_alias=None, to_address=None, amount=None, tzkt_transaction_id=None, tzkt_origination_id=None, tzkt_migration_id=None) +class dipdup.models.tezos.TezosTokenTransferData(id, level, timestamp, tzkt_token_id, contract_address=None, contract_alias=None, token_id=None, standard=None, metadata=None, from_alias=None, from_address=None, to_alias=None, to_address=None, amount=None, tzkt_transaction_id=None, tzkt_origination_id=None, tzkt_migration_id=None)

                      Basic structure for token transver received from TzKT SignalR API

                      Parameters:
                      @@ -828,7 +828,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosTransaction -class dipdup.models.tezos.TezosTransaction(data, parameter, storage) +class dipdup.models.tezos.TezosTransaction(data, parameter, storage)

                      Wrapper for matched transaction with typed data passed to the handler

                      Parameters:
                      @@ -845,7 +845,7 @@ description: "Models reference" ## dipdup.models.tezos.TezosUnknownEvent -class dipdup.models.tezos.TezosUnknownEvent(data, payload) +class dipdup.models.tezos.TezosUnknownEvent(data, payload)
                      Parameters:
                        @@ -865,7 +865,7 @@ description: "Models reference" ## dipdup.models.coinbase.CoinbaseCandleData -class dipdup.models.coinbase.CoinbaseCandleData(timestamp, low, high, open, close, volume) +class dipdup.models.coinbase.CoinbaseCandleData(timestamp, low, high, open, close, volume)
                        Parameters:
                          @@ -884,14 +884,14 @@ description: "Models reference" ## dipdup.models.coinbase.CoinbaseCandleInterval -class dipdup.models.coinbase.CoinbaseCandleInterval(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.coinbase.CoinbaseCandleInterval(*values)
                        ## dipdup.models.tzip_metadata.TzipMetadataNetwork -class dipdup.models.tzip_metadata.TzipMetadataNetwork(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None) +class dipdup.models.tzip_metadata.TzipMetadataNetwork(*values)

                        Tezos network enum for TZIP-16 metadata.

                        Parameters:
                        diff --git a/docs/9.release-notes/1.v8.3.md b/docs/9.release-notes/1.v8.3.md new file mode 100644 index 000000000..7bd837d33 --- /dev/null +++ b/docs/9.release-notes/1.v8.3.md @@ -0,0 +1,13 @@ +--- +title: "8.3" +description: DipDup 8.3 release notes +--- + + + +# Release Notes: 8.3 + +... + +{{ #include 9.release-notes/_8.3_changelog.md }} +{{ #include 9.release-notes/_footer.md }} diff --git a/docs/9.release-notes/9.v7.0.md b/docs/9.release-notes/10.v7.0.md similarity index 100% rename from docs/9.release-notes/9.v7.0.md rename to docs/9.release-notes/10.v7.0.md diff --git a/docs/9.release-notes/1.v8.2.md b/docs/9.release-notes/2.v8.2.md similarity index 100% rename from docs/9.release-notes/1.v8.2.md rename to docs/9.release-notes/2.v8.2.md diff --git a/docs/9.release-notes/2.v8.1.md b/docs/9.release-notes/3.v8.1.md similarity index 100% rename from docs/9.release-notes/2.v8.1.md rename to docs/9.release-notes/3.v8.1.md diff --git a/docs/9.release-notes/3.v8.0.md b/docs/9.release-notes/4.v8.0.md similarity index 100% rename from docs/9.release-notes/3.v8.0.md rename to docs/9.release-notes/4.v8.0.md diff --git a/docs/9.release-notes/4.v7.5.md b/docs/9.release-notes/5.v7.5.md similarity index 100% rename from docs/9.release-notes/4.v7.5.md rename to docs/9.release-notes/5.v7.5.md diff --git a/docs/9.release-notes/5.v7.4.md b/docs/9.release-notes/6.v7.4.md similarity index 100% rename from docs/9.release-notes/5.v7.4.md rename to docs/9.release-notes/6.v7.4.md diff --git a/docs/9.release-notes/6.v7.3.md b/docs/9.release-notes/7.v7.3.md similarity index 100% rename from docs/9.release-notes/6.v7.3.md rename to docs/9.release-notes/7.v7.3.md diff --git a/docs/9.release-notes/7.v7.2.md b/docs/9.release-notes/8.v7.2.md similarity index 100% rename from docs/9.release-notes/7.v7.2.md rename to docs/9.release-notes/8.v7.2.md diff --git a/docs/9.release-notes/8.v7.1.md b/docs/9.release-notes/9.v7.1.md similarity index 100% rename from docs/9.release-notes/8.v7.1.md rename to docs/9.release-notes/9.v7.1.md diff --git a/docs/9.release-notes/_8.3_changelog.md b/docs/9.release-notes/_8.3_changelog.md new file mode 100644 index 000000000..e69de29bb diff --git a/indexer_state.py b/indexer_state.py deleted file mode 100644 index f680d7f31..000000000 --- a/indexer_state.py +++ /dev/null @@ -1,20 +0,0 @@ -from headmcp import HeadMCP - -def get_indexer_state(): - # Initialize Head MCP client - head_mcp = HeadMCP() - - # Fetch the current indexer state - indexer_state = head_mcp.get_indexer_state() - - return indexer_state - -def main(): - try: - state = get_indexer_state() - print(f"Current Indexer State: {state}") - except Exception as e: - print(f"Error fetching indexer state: {e}") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8dd56fada..1bc941622 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,30 +1,30 @@ # This file was autogenerated by uv via the following command: # uv export --all-extras --locked --no-group lint --no-group test --no-group docs --no-group perf -e . -aerich==0.8.1 \ - --hash=sha256:1e95b1c04dfc0c634dd43b0123933038c820140e17a4b27885a63b7461eb0632 \ - --hash=sha256:2743cf85bd9957ea173055dad07ee5a3219067e4f117d5402a44204c27e83c9f -aiohappyeyeballs==2.4.6 \ - --hash=sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1 \ - --hash=sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0 -aiohttp==3.11.12 \ - --hash=sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a \ - --hash=sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0 \ - --hash=sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802 \ - --hash=sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef \ - --hash=sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e \ - --hash=sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0 \ - --hash=sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1 \ - --hash=sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259 \ - --hash=sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0 \ - --hash=sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9 \ - --hash=sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9 \ - --hash=sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f \ - --hash=sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9 \ - --hash=sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df \ - --hash=sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c \ - --hash=sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d \ - --hash=sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250 +aerich==0.8.2 \ + --hash=sha256:0ed2b15bb017785d17323329e5448b4b12fcfec08a20932626c12aa59a8378d6 \ + --hash=sha256:c1000cebd8525a1310a0cf0ea7d9a03cc2529413f182e90f08a6c6f4f61edcda +aiohappyeyeballs==2.6.1 \ + --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \ + --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 +aiohttp==3.11.13 \ + --hash=sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90 \ + --hash=sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8 \ + --hash=sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e \ + --hash=sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f \ + --hash=sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d \ + --hash=sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117 \ + --hash=sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee \ + --hash=sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d \ + --hash=sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778 \ + --hash=sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb \ + --hash=sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb \ + --hash=sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820 \ + --hash=sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496 \ + --hash=sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637 \ + --hash=sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2 \ + --hash=sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b \ + --hash=sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a aiolimiter==1.2.1 \ --hash=sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7 \ --hash=sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9 @@ -49,9 +49,9 @@ appdirs==1.4.4 \ apscheduler==3.11.0 \ --hash=sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133 \ --hash=sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da -argcomplete==3.5.3 \ - --hash=sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61 \ - --hash=sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392 +argcomplete==3.6.0 \ + --hash=sha256:2e4e42ec0ba2fff54b0d244d0b1623e86057673e57bafe72dda59c64bd5dee8b \ + --hash=sha256:4e3e4e10beb20e06444dbac0ac8dda650cb6349caeefe980208d3c548708bedd asgiref==3.8.1 \ --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \ --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590 @@ -72,29 +72,28 @@ asyncpg==0.30.0 \ --hash=sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851 \ --hash=sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e \ --hash=sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af -attrs==25.1.0 \ - --hash=sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e \ - --hash=sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b base58==2.1.1 \ --hash=sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 \ --hash=sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c -bitarray==3.0.0 \ - --hash=sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f \ - --hash=sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d \ - --hash=sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3 \ - --hash=sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942 \ - --hash=sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755 \ - --hash=sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc \ - --hash=sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80 \ - --hash=sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c \ - --hash=sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9 \ - --hash=sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b \ - --hash=sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4 \ - --hash=sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03 \ - --hash=sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1 \ - --hash=sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b \ - --hash=sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999 \ - --hash=sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a +bitarray==3.1.1 \ + --hash=sha256:079a2fd318d2300c2ef6e84fe9f8f563bcfad46b1360b7d58d82711e5bd8ea56 \ + --hash=sha256:1af43aa945c595bff4386a2b64c46f6cee653883e295907e419f6745123a168f \ + --hash=sha256:1dffa70fdf6e4aba4e4f8c8aa408950c3199f71546fb24cafc70f03400b22a59 \ + --hash=sha256:21081fb49d6b0a19b238d30dc0c948bec365bf16fccc2a1478a2794b37a6a812 \ + --hash=sha256:41f0f93a3ccde6f0e3b953d5adc89f7d4acdd3aadf71af8353c8b288a9d4bd80 \ + --hash=sha256:514757d0f76d9f84187ea15debd8b3956438f6f4f1e600be1019facdb6076796 \ + --hash=sha256:51f860b200fa90e446db77fe57173603ddb81eef4d4ba1ccdc277b564b3f20ab \ + --hash=sha256:5c819daa6c11107614328d9018ccbaf5b7c64060172bf564373d62928f139e87 \ + --hash=sha256:6a3816df3db86feaa8b4efd6844c6808147954626898355253e11e2544145f48 \ + --hash=sha256:7aceaaff83bcdf87bcc13788e29fd068d1a1e6965946ffed80f6e9323e5edd3d \ + --hash=sha256:97825938fa3fd7e917e40d66ab70ff133dee385da13cf91e0bd5cd4ec2d97048 \ + --hash=sha256:9e43e1a8b38d6e8ccc27cad74dabd8005b078856c471a1d0537491b71184f209 \ + --hash=sha256:a3c1d74ac2c969bac33169286fe601f8a6f4ca0e8f26dbaa22ad61fbf8fcf259 \ + --hash=sha256:a44c5271f7e079530f7a5eecac55cb4ff350ac82db768c550f66c1419893e4c7 \ + --hash=sha256:f913548d2907633782f0c3de72fa28646e1f97bdaf7f2df04014a17e923a258b black==25.1.0 \ --hash=sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc \ --hash=sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666 \ @@ -124,17 +123,17 @@ charset-normalizer==3.4.1 \ --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 -ckzg==2.0.1 \ - --hash=sha256:1fd9fb690c88919f30c9f3ab7cc46a7ecd734d5ff4c9ccea383c119b9b7cc4da \ - --hash=sha256:285cf3121b8a8c5609c5b706314f68d2ba2784ab02c5bb7487c6ae1714ecb27f \ - --hash=sha256:2eb50c53efdb9c34f762bd0c8006cf79bc92a9daf47aa6b541e496988484124f \ - --hash=sha256:2f927bc41c2551b0ef0056a649a7ebed29d9665680a10795f4cee5002c69ddb7 \ - --hash=sha256:62c5adc381637affa7e1df465c57750b356a761b8a3164c3106589b02532b9c9 \ - --hash=sha256:7960cc62f959403293fb53a3c2404778369ae7cefc6d7f202e5e00567cf98c4b \ - --hash=sha256:d721bcd492294c70eca39da0b0a433c29b6a571dbac2f7084bab06334904af06 \ - --hash=sha256:dde2391d025b5033ef0eeacf62b11ecfe446aea25682b5f547a907766ad0a8cb \ - --hash=sha256:fab8859d9420f6f7df4e094ee3639bc49d18c8dab0df81bee825e2363dd67a09 \ - --hash=sha256:fabc3bd41b306d1c7025d561c3281a007c2aca8ceaf998582dc3894904d9c73e +ckzg==2.1.0 \ + --hash=sha256:0959685678e3b89d740412f6d7ae0821b74ccbeac04080cb066774ea3044e2e9 \ + --hash=sha256:1bc4c46b0d7db4dd88b55cbd40d13e193536dcd5ae5d0634f0d838de832f429e \ + --hash=sha256:516610ac84f414338b0c1dc41a423906503e34b6672450e50cf22a21a707e51f \ + --hash=sha256:5a4a3de4f0e264c6d1676379a8968aef681f14df7b1144b7a9ba391180f33510 \ + --hash=sha256:73a353f31c945f0617a6b98d82fbb23909ac5039a10e345c681b728dd917b51a \ + --hash=sha256:9d9b35cf921a68769dce6fb64c34a7c331e6f7b8055bfbb8661e7163180f2742 \ + --hash=sha256:a13ce05bc4d2b93aa4f8aaecf9d785878f3d319a05e499268035a1ab1d464d52 \ + --hash=sha256:cecfafea88ef8106440e22eb6db56bf702f30d3af8a3fb54b2a628a5c4e10056 \ + --hash=sha256:e28a995e3b2923b05adb023412943dfd3b1aa1ca4e3a93d2149dcfbc15de639f \ + --hash=sha256:fb1054189f4c6b83d19e2c1a65521485227eb3b63fa3211adccaa7c587befc2a click==8.1.8 \ --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a @@ -172,9 +171,9 @@ cytoolz==1.0.1 ; implementation_name == 'cpython' \ --hash=sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0 \ --hash=sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9 \ --hash=sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4 -datamodel-code-generator==0.28.1 \ - --hash=sha256:1ff8a56f9550a82bcba3e1ad7ebdb89bc655eeabbc4bc6acfb05977cbdc6381c \ - --hash=sha256:37ef5f3b488f7d7a3f0b5b3ba0f2bc1ae01bab4dc7e0f6b99ff6c40713a6beb3 +datamodel-code-generator==0.28.4 \ + --hash=sha256:16a58933e7c2de692c88e0061ba9a7e23009fe298a726cbba027574fff25c9ba \ + --hash=sha256:ae1056643cf3cdd298ef4b622085158ec7ef1722f6da1c20e7022c942f2e3ebb dictdiffer==0.9.0 \ --hash=sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578 \ --hash=sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595 @@ -199,9 +198,9 @@ eth-keys==0.6.1 \ eth-rlp==2.2.0 \ --hash=sha256:5692d595a741fbaef1203db6a2fedffbd2506d31455a6ad378c8449ee5985c47 \ --hash=sha256:5e4b2eb1b8213e303d6a232dfe35ab8c29e2d3051b86e8d359def80cd21db83d -eth-typing==5.1.0 \ - --hash=sha256:8581f212ee6252aaa285377a77620f6e5f6e16ac3f144c61f098fafd47967b1a \ - --hash=sha256:c0d6b93f5385aa84efc4b47ae2bd478da069bc0ffda8b67e0ccb573f43defd29 +eth-typing==5.2.0 \ + --hash=sha256:28685f7e2270ea0d209b75bdef76d8ecef27703e1a16399f6929820d05071c28 \ + --hash=sha256:e1f424e97990fc3c6a1c05a7b0968caed4e20e9c99a4d5f4db3df418e25ddc80 eth-utils==5.2.0 \ --hash=sha256:17e474eb654df6e18f20797b22c6caabb77415a996b3ba0f3cc8df3437463134 \ --hash=sha256:4d43eeb6720e89a042ad5b28d4b2111630ae764f444b85cbafb708d7f076da10 @@ -226,9 +225,21 @@ frozenlist==1.5.0 \ genson==1.3.0 \ --hash=sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7 \ --hash=sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37 +h11==0.14.0 \ + --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ + --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 hexbytes==1.3.0 \ --hash=sha256:4a61840c24b0909a6534350e2d28ee50159ca1c9e89ce275fd31c110312cf684 \ --hash=sha256:83720b529c6e15ed21627962938dc2dec9bb1010f17bbbd66bf1e6a8287d522c +httpcore==1.0.7 \ + --hash=sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c \ + --hash=sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad +httpx-sse==0.4.0 \ + --hash=sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721 \ + --hash=sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f idna==3.10 \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 @@ -238,12 +249,12 @@ inflect==5.6.2 \ iso8601==2.1.0 \ --hash=sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df \ --hash=sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242 -isort==6.0.0 \ - --hash=sha256:567954102bb47bb12e0fae62606570faacddd441e45683968c8d1734fb1af892 \ - --hash=sha256:75d9d8a1438a9432a7d7b54f2d3b45cad9a4a0fdba43617d9873379704a8bdf1 -jinja2==3.1.5 \ - --hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \ - --hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb +isort==6.0.1 \ + --hash=sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450 \ + --hash=sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615 +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 lark==1.2.2 \ --hash=sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c \ --hash=sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80 @@ -283,6 +294,9 @@ marshmallow-dataclass==8.7.1 \ marshmallow-oneofschema==3.1.1 \ --hash=sha256:68b4a57d0281a04ac25d4eb7a4c5865a57090a0a8fd30fd6362c8e833ac6a6d9 \ --hash=sha256:ff4cb2a488785ee8edd521a765682c2c80c78b9dc48894124531bdfa1ec9303b +mcp==1.4.1 \ + --hash=sha256:a7716b1ec1c054e76f49806f7d96113b99fc1166fc9244c2c6f19867cb75b593 \ + --hash=sha256:b9655d2de6313f9d55a7d1df62b3c3fe27a530100cc85bf23729145b0dba4c7a more-itertools==10.6.0 \ --hash=sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b \ --hash=sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89 @@ -368,42 +382,42 @@ poseidon-py==0.1.5 \ prometheus-client==0.21.1 \ --hash=sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb \ --hash=sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301 -propcache==0.2.1 \ - --hash=sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4 \ - --hash=sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a \ - --hash=sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d \ - --hash=sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6 \ - --hash=sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d \ - --hash=sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348 \ - --hash=sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64 \ - --hash=sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54 \ - --hash=sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24 \ - --hash=sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5 \ - --hash=sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518 \ - --hash=sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff \ - --hash=sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1 \ - --hash=sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246 \ - --hash=sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0 \ - --hash=sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f \ - --hash=sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec \ - --hash=sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6 +propcache==0.3.0 \ + --hash=sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57 \ + --hash=sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64 \ + --hash=sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e \ + --hash=sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b \ + --hash=sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043 \ + --hash=sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d \ + --hash=sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138 \ + --hash=sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c \ + --hash=sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7 \ + --hash=sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7 \ + --hash=sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667 \ + --hash=sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86 \ + --hash=sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af \ + --hash=sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5 \ + --hash=sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf \ + --hash=sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d \ + --hash=sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e \ + --hash=sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5 py-ecc==7.0.1 \ --hash=sha256:557461f42e57294d734305a30faf6b8903421651871e9cdeff8d8e67c6796c70 \ --hash=sha256:84a8b4d436163c83c65345a68e32f921ef6e64374a36f8e561f0455b4b08f5f2 -pycryptodome==3.21.0 \ - --hash=sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8 \ - --hash=sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93 \ - --hash=sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4 \ - --hash=sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764 \ - --hash=sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca \ - --hash=sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e \ - --hash=sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd \ - --hash=sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1 \ - --hash=sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a \ - --hash=sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2 \ - --hash=sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53 \ - --hash=sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b \ - --hash=sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297 +pycryptodome==3.22.0 \ + --hash=sha256:009e1c80eea42401a5bd5983c4bab8d516aef22e014a4705622e24e6d9d703c6 \ + --hash=sha256:18d5b0ddc7cf69231736d778bd3ae2b3efb681ae33b64b0c92fb4626bb48bb89 \ + --hash=sha256:37ddcd18284e6b36b0a71ea495a4c4dca35bb09ccc9bfd5b91bfaf2321f131c1 \ + --hash=sha256:3b76fa80daeff9519d7e9f6d9e40708f2fce36b9295a847f00624a08293f4f00 \ + --hash=sha256:98fd9da809d5675f3a65dcd9ed384b9dc67edab6a4cda150c5870a8122ec961d \ + --hash=sha256:a0092fd476701eeeb04df5cc509d8b739fa381583cda6a46ff0a60639b7cd70d \ + --hash=sha256:a31fa5914b255ab62aac9265654292ce0404f6b66540a065f538466474baedbc \ + --hash=sha256:aec7b40a7ea5af7c40f8837adf20a137d5e11a6eb202cde7e588a48fb2d871a8 \ + --hash=sha256:d086aed307e96d40c23c42418cbbca22ecc0ab4a8a0e24f87932eeab26c08627 \ + --hash=sha256:d21c1eda2f42211f18a25db4eaf8056c94a8563cd39da3683f89fe0d881fb772 \ + --hash=sha256:f02baa9f5e35934c6e8dcec91fcde96612bdefef6e442813b8ea34e82c84bbfb \ + --hash=sha256:f6cf6aa36fcf463e622d2165a5ad9963b2762bebae2f632d719dfb8544903cf5 \ + --hash=sha256:fd7ab568b3ad7b77c908d7c3f7e167ec5a8f035c64ff74f10d47a4edd043d723 pydantic==2.10.6 \ --hash=sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584 \ --hash=sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236 @@ -423,15 +437,18 @@ pydantic-core==2.27.2 \ --hash=sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9 \ --hash=sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3 \ --hash=sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39 +pydantic-settings==2.8.1 \ + --hash=sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c \ + --hash=sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585 pyhumps==3.8.0 \ --hash=sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6 \ --hash=sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3 pypika-tortoise==0.5.0 \ --hash=sha256:dbdc47eb52ce17407b05ce9f8560ce93b856d7b28beb01971d956b017846691f \ --hash=sha256:ed0f56761868dc222c03e477578638590b972280b03c7c45cd93345b18b61f58 -pysignalr==1.1.0 \ - --hash=sha256:17977720e3360403b05e8c764a28c70a3865fef973cc0f08bd877101f06ae815 \ - --hash=sha256:f9e9ed611c999043778ebb9e5bd7d92e0afae3f9555eb73d447335e48de3b385 +pysignalr==1.2.0 \ + --hash=sha256:68ad9fb2e1ae76673537e4b35fd88317ed6585ec664756177005896d19d9967b \ + --hash=sha256:e7ed0c4cb54523cf0c33a0a63ce8422f94b6cbac88f1cae597fe40770c7cbbe9 python-dotenv==1.0.1 \ --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a @@ -499,9 +516,9 @@ ruamel-yaml-clib==0.2.12 ; platform_python_implementation == 'CPython' \ scalecodec==1.2.11 \ --hash=sha256:99a2cdbfccdcaf22bd86b86da55a730a2855514ad2309faef4a4a93ac6cbeb8d \ --hash=sha256:d15c94965f617caa25096f83a45f5f73031d05e6ee08d6039969f0a64fc35de1 -sentry-sdk==2.21.0 \ - --hash=sha256:7623cfa9e2c8150948a81ca253b8e2bfe4ce0b96ab12f8cd78e3ac9c490fd92f \ - --hash=sha256:a6d38e0fb35edda191acf80b188ec713c863aaa5ad8d5798decb8671d02077b6 +sentry-sdk==2.22.0 \ + --hash=sha256:3d791d631a6c97aad4da7074081a57073126c69487560c6f8bffcf586461de66 \ + --hash=sha256:b4bf43bb38f547c84b2eadcefbe389b36ef75f3f38253d7a74d6b928c07ae944 six==1.17.0 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 @@ -511,8 +528,14 @@ sniffio==1.3.1 \ sqlparse==0.5.3 \ --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca +sse-starlette==2.2.1 \ + --hash=sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419 \ + --hash=sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99 starknet-py==0.25.0 \ --hash=sha256:452f2c9ed5e235540209ec2042d22dcf79c6cec2a6850f5b518d61b3fada3706 +starlette==0.46.1 \ + --hash=sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230 \ + --hash=sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227 strict-rfc3339==0.7 \ --hash=sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277 survey==5.4.2 \ @@ -533,9 +556,9 @@ tortoise-orm==0.24.0 \ typeguard==4.4.2 \ --hash=sha256:77a78f11f09777aeae7fa08585f33b5f4ef0e7335af40005b0c422ed398ff48c \ --hash=sha256:a6f1065813e32ef365bc3b3f503af8a96f9dd4e0033a02c28c4a4983de8c6c49 -types-requests==2.32.0.20241016 \ - --hash=sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95 \ - --hash=sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747 +types-requests==2.32.0.20250306 \ + --hash=sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1 \ + --hash=sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 @@ -545,12 +568,15 @@ typing-inspect==0.9.0 \ tzdata==2025.1 ; sys_platform == 'win32' \ --hash=sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694 \ --hash=sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639 -tzlocal==5.3 \ - --hash=sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2 \ - --hash=sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c +tzlocal==5.3.1 \ + --hash=sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd \ + --hash=sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d urllib3==2.3.0 \ --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d +uvicorn==0.34.0 \ + --hash=sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4 \ + --hash=sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9 uvloop==0.21.0 \ --hash=sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f \ --hash=sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c \ @@ -559,9 +585,9 @@ uvloop==0.21.0 \ --hash=sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc \ --hash=sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d \ --hash=sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2 -web3==7.8.0 \ - --hash=sha256:712bc9fd6b1ef6e467ee24c25b581e1951cab2cba17f9f548f12587734f2c857 \ - --hash=sha256:c8771b3d8772f7104a0462804449beb57d36cef7bd8b411140f95a92fc46b559 +web3==7.9.0 \ + --hash=sha256:3c4487a7ac57e0a187bd7ee03db455d94f354d15cca45f097f15f7281ad1a01f \ + --hash=sha256:7818267675283e9cae4487d2805fc34b899aa26f41b0000c798c79b1684899eb websocket-client==1.8.0 \ --hash=sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526 \ --hash=sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da diff --git a/src/demo_evm_uniswap/handlers/factory/pool_created.py b/src/demo_evm_uniswap/handlers/factory/pool_created.py index 637b1890f..36eaa74c6 100644 --- a/src/demo_evm_uniswap/handlers/factory/pool_created.py +++ b/src/demo_evm_uniswap/handlers/factory/pool_created.py @@ -1,15 +1,18 @@ from contextlib import suppress +from typing import TYPE_CHECKING from typing import cast from demo_evm_uniswap import models as models from demo_evm_uniswap.models.token import WHITELIST_TOKENS from demo_evm_uniswap.models.token import ERC20Token from demo_evm_uniswap.types.factory.evm_events.pool_created import PoolCreatedPayload -from dipdup.config.evm import EvmContractConfig from dipdup.context import HandlerContext from dipdup.models.evm import EvmEvent from tortoise.exceptions import OperationalError +if TYPE_CHECKING: + from dipdup.config.evm import EvmContractConfig + POOL_BLACKLIST = {'0x8fe8d9bb8eeba3ed688069c3d6b556c9ca258248'} WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' @@ -42,7 +45,7 @@ async def pool_created( ctx.logger.info('Pool %s is blacklisted', event.payload.pool) return - factory_address = cast(EvmContractConfig, ctx.config.get_contract('factory')).address + factory_address = cast('EvmContractConfig', ctx.config.get_contract('factory')).address factory, _ = await models.Factory.get_or_create(id=factory_address) factory.pool_count += 1 await factory.save() diff --git a/src/demo_evm_uniswap/models/abi.py b/src/demo_evm_uniswap/models/abi.py index 0c4dd2a87..9749882ca 100644 --- a/src/demo_evm_uniswap/models/abi.py +++ b/src/demo_evm_uniswap/models/abi.py @@ -9,4 +9,4 @@ def get_abi(path: str) -> dict[str, Any]: """Get ABI without context to cache in the module""" package_dir = Path(__file__).parent.parent abi_path = package_dir / 'abi' / f"{path.replace('.', '/')}.json" - return cast(dict[str, Any], orjson.loads(abi_path.read_bytes())) + return cast('dict[str, Any]', orjson.loads(abi_path.read_bytes())) diff --git a/src/demo_evm_uniswap/models/repo.py b/src/demo_evm_uniswap/models/repo.py index a3a27c9c9..cdaea4177 100644 --- a/src/demo_evm_uniswap/models/repo.py +++ b/src/demo_evm_uniswap/models/repo.py @@ -1,13 +1,16 @@ from decimal import Decimal +from typing import TYPE_CHECKING from typing import Any from typing import cast -from dipdup.config.evm import EvmContractConfig from dipdup.context import HandlerContext from lru import LRU import demo_evm_uniswap.models as models +if TYPE_CHECKING: + from dipdup.config.evm import EvmContractConfig + USDC_WETH_03_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8' @@ -36,7 +39,7 @@ def get_pending_position(self, idx: str) -> dict[str, Any] | None: async def get_ctx_factory(ctx: HandlerContext) -> models.Factory: - factory_address = cast(EvmContractConfig, ctx.config.get_contract('factory')).address + factory_address = cast('EvmContractConfig', ctx.config.get_contract('factory')).address if factory_address is None: raise Exception('Factory address is not specified') return await models.Factory.cached_get(factory_address) diff --git a/src/demo_tezos_domains/handlers/on_update_expiry_map.py b/src/demo_tezos_domains/handlers/on_update_expiry_map.py index f0dbacd7c..3eb8999b3 100644 --- a/src/demo_tezos_domains/handlers/on_update_expiry_map.py +++ b/src/demo_tezos_domains/handlers/on_update_expiry_map.py @@ -41,7 +41,7 @@ async def on_update_expiry_map( record.expired = False await record.save() if record.address is not None: - metadata = {} if record.metadata is None else cast(dict[str, Any], record.metadata) + metadata = {} if record.metadata is None else cast('dict[str, Any]', record.metadata) metadata.update(name=record.id) await ctx.update_contract_metadata( network=ctx.handler_config.parent.datasources[0].name, diff --git a/src/demo_tezos_domains/hooks/check_expiration.py b/src/demo_tezos_domains/hooks/check_expiration.py index d996f6290..147c44c76 100644 --- a/src/demo_tezos_domains/hooks/check_expiration.py +++ b/src/demo_tezos_domains/hooks/check_expiration.py @@ -1,15 +1,18 @@ from datetime import datetime +from typing import TYPE_CHECKING from typing import cast from demo_tezos_domains.models import Record from dipdup.context import HookContext -from dipdup.datasources.tezos_tzkt import TezosTzktDatasource + +if TYPE_CHECKING: + from dipdup.datasources.tezos_tzkt import TezosTzktDatasource async def check_expiration( ctx: HookContext, ) -> None: - ds = cast(TezosTzktDatasource, next(iter(ctx.datasources.values()))) + ds = cast('TezosTzktDatasource', next(iter(ctx.datasources.values()))) expiring_records = ( await Record.filter(expired=False, domain__expires_at__lt=datetime.utcnow()).all().prefetch_related('domain') ) diff --git a/src/demo_tezos_factories/handlers/on_factory_origination.py b/src/demo_tezos_factories/handlers/on_factory_origination.py index 719311c79..76650e619 100644 --- a/src/demo_tezos_factories/handlers/on_factory_origination.py +++ b/src/demo_tezos_factories/handlers/on_factory_origination.py @@ -12,8 +12,8 @@ async def on_factory_origination( origination_1: TezosOperationData, ) -> None: assert transaction_0.parameter_json - dex_contract = cast(str, origination_1.originated_contract_address) - token_contract = cast(str, transaction_0.parameter_json['token']['address']) + dex_contract = cast('str', origination_1.originated_contract_address) + token_contract = cast('str', transaction_0.parameter_json['token']['address']) await ctx.add_contract( kind='tezos', diff --git a/src/dipdup/_version.py b/src/dipdup/_version.py index e179e4ed7..6e891e373 100644 --- a/src/dipdup/_version.py +++ b/src/dipdup/_version.py @@ -63,7 +63,7 @@ async def _get_latest_version() -> str | None: try: response = await session.get(RELEASES_URL) response_json = await response.json() - return cast(str, response_json['tag_name']) + return cast('str', response_json['tag_name']) except Exception as e: _logger.debug('Failed to get the latest version from GitHub: %s', e) return None @@ -81,7 +81,7 @@ def _read_cached_version() -> str | None: try: cached_version = cast( - CachedVersion, + 'CachedVersion', json.loads(CACHE_PATH.read_bytes()), ) # NOTE: Invalidate cache if installed version is different diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 77e6f933c..6a3b16530 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -198,7 +198,7 @@ def wrapper(ctx: click.Context, *args: Any, **kwargs: Any) -> None: package = ctx.obj.config.package save_report(package, None) - return cast(WrappedCommandT, wrapper) + return cast('WrappedCommandT', wrapper) def _cli_unwrapper(cmd: click.Command) -> Callable[..., Coroutine[Any, Any, None]]: @@ -585,7 +585,7 @@ async def hasura_configure(ctx: click.Context, force: bool) -> None: hasura_gateway = HasuraGateway( package=config.package, hasura_config=config.hasura, - database_config=cast(PostgresDatabaseConfig, config.database), + database_config=cast('PostgresDatabaseConfig', config.database), ) async with AsyncExitStack() as stack: diff --git a/src/dipdup/codegen/evm.py b/src/dipdup/codegen/evm.py index 1f0dbc8aa..b69b092c7 100644 --- a/src/dipdup/codegen/evm.py +++ b/src/dipdup/codegen/evm.py @@ -82,7 +82,7 @@ async def _fetch_abi(self, index_config: EvmIndexConfigU) -> None: # deduplicated (by name) Datasource list datasources: list[AbiDatasource[Any]] = list( { - datasource_config.name: cast(AbiDatasource[Any], self._datasources[datasource_config.name]) + datasource_config.name: cast('AbiDatasource[Any]', self._datasources[datasource_config.name]) for datasource_config in index_config.datasources if isinstance(datasource_config, EvmEtherscanDatasourceConfig) }.values() diff --git a/src/dipdup/codegen/starknet.py b/src/dipdup/codegen/starknet.py index 12c1c2b52..022db16ee 100644 --- a/src/dipdup/codegen/starknet.py +++ b/src/dipdup/codegen/starknet.py @@ -37,7 +37,7 @@ async def _fetch_abi(self, index_config: StarknetEventsIndexConfig) -> None: # deduplicated (by name) Datasource list datasources: list[AbiDatasource[Any]] = list( { - datasource_config.name: cast(AbiDatasource[Any], self._datasources[datasource_config.name]) + datasource_config.name: cast('AbiDatasource[Any]', self._datasources[datasource_config.name]) for datasource_config in index_config.datasources if isinstance(datasource_config, StarknetNodeDatasourceConfig) }.values() diff --git a/src/dipdup/codegen/substrate.py b/src/dipdup/codegen/substrate.py index 3509c0dd7..88330d21c 100644 --- a/src/dipdup/codegen/substrate.py +++ b/src/dipdup/codegen/substrate.py @@ -137,7 +137,7 @@ async def generate_abis(self) -> None: for datasource_config in index_config.datasources: if isinstance(datasource_config, SubstrateSubscanDatasourceConfig): - datasource = cast(SubstrateSubscanDatasource, self._datasources[datasource_config.name]) + datasource = cast('SubstrateSubscanDatasource', self._datasources[datasource_config.name]) break else: raise NotImplementedError('Codegen currently requires `substrate.subscan` datasource') diff --git a/src/dipdup/codegen/tezos.py b/src/dipdup/codegen/tezos.py index 5e11f5b20..09145a68a 100644 --- a/src/dipdup/codegen/tezos.py +++ b/src/dipdup/codegen/tezos.py @@ -77,7 +77,7 @@ def preprocess_storage_jsonschema(schema: dict[str, Any]) -> dict[str, Any]: 'additionalProperties': preprocess_storage_jsonschema(schema['additionalProperties']), } if schema.get('$comment') == 'big_map': - return cast(dict[str, Any], schema['oneOf'][1]) + return cast('dict[str, Any]', schema['oneOf'][1]) return schema @@ -123,7 +123,7 @@ async def generate_schemas(self) -> None: for index_config in self._config.indexes.values(): if isinstance(index_config, TezosOperationsIndexConfig): await self._fetch_operation_index_schema(index_config) - template = cast(TezosOperationsIndexConfig, index_config._parent) + template = cast('TezosOperationsIndexConfig', index_config._parent) if template in unused_operation_templates: unused_operation_templates.remove(template) elif isinstance(index_config, TezosBigMapsIndexConfig): @@ -273,7 +273,7 @@ async def _fetch_operation_pattern_schema( return parameter_schemas_path = contract_schemas_path / 'tezos_parameters' - entrypoint = cast(str, operation_pattern_config.entrypoint) + entrypoint = cast('str', operation_pattern_config.entrypoint) try: entrypoint_schema = match_entrypoint_schema( @@ -375,7 +375,7 @@ async def get_schemas( self._logger.info('Fetching schemas for contract `%s`', address) address_schemas_json = await datasource.get_jsonschemas(address) schemas[datasource.name][address] = address_schemas_json - return cast(dict[str, Any], schemas[datasource.name][address]) + return cast('dict[str, Any]', schemas[datasource.name][address]) def get_storage_type(package: DipDupPackage, typename: str) -> TypeClass: diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index 64f98780b..fb7fd242d 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -966,7 +966,7 @@ def add_index( ) template_config._name = name self._resolve_template(template_config) - index_config = cast(ResolvedIndexConfigU, self.indexes[name]) + index_config = cast('ResolvedIndexConfigU', self.indexes[name]) self._resolve_index_links(index_config) index_config._name = name @@ -1195,7 +1195,7 @@ def _resolve_index_links(self, index_config: ResolvedIndexConfigU) -> None: def _set_names(self) -> None: named_config_sections = cast( - tuple[dict[str, NameMixin], ...], + 'tuple[dict[str, NameMixin], ...]', ( self.contracts, self.datasources, diff --git a/src/dipdup/config/_mixin.py b/src/dipdup/config/_mixin.py index 98890924d..a3ec78c1c 100644 --- a/src/dipdup/config/_mixin.py +++ b/src/dipdup/config/_mixin.py @@ -64,7 +64,7 @@ def locate_arguments(self) -> dict[str, type | None]: kwargs: dict[str, type[Any] | None] = {} for name, cls in self.iter_arguments(): cls = cls.split(' as ')[0] - kwargs[name] = cast(type | None, locate(cls)) + kwargs[name] = cast('type | None', locate(cls)) return kwargs diff --git a/src/dipdup/config/substrate_events.py b/src/dipdup/config/substrate_events.py index 5e008b656..04226b0c1 100644 --- a/src/dipdup/config/substrate_events.py +++ b/src/dipdup/config/substrate_events.py @@ -40,7 +40,7 @@ def iter_imports(self, package: str) -> Iterator[tuple[str, str]]: event_cls = snake_to_pascal(self.name) + 'Payload' event_module = pascal_to_snake(self.name.replace('.', '')) - parent = cast(SubstrateIndexConfig, self.parent) + 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]]: diff --git a/src/dipdup/config/tezos_operations.py b/src/dipdup/config/tezos_operations.py index e6fa68bfe..2ba2b67cb 100644 --- a/src/dipdup/config/tezos_operations.py +++ b/src/dipdup/config/tezos_operations.py @@ -139,7 +139,7 @@ def iter_imports(self, package: str) -> Iterator[tuple[str, str]]: yield self.format_parameter_import( package, module_name, - cast(str, self.entrypoint), + cast('str', self.entrypoint), self.alias, ) yield self.format_storage_import(package, module_name) @@ -151,7 +151,7 @@ def iter_arguments(self) -> Iterator[tuple[str, str]]: module_name = self.typed_contract.module_name yield self.format_operation_argument( module_name, - cast(str, self.entrypoint), + cast('str', self.entrypoint), self.optional, self.alias, ) diff --git a/src/dipdup/database.py b/src/dipdup/database.py index 7870ac11e..97ff8868c 100644 --- a/src/dipdup/database.py +++ b/src/dipdup/database.py @@ -48,7 +48,7 @@ def get_connection() -> SupportedClient: - return cast(SupportedClient, connections.get(DEFAULT_CONNECTION_NAME)) + return cast('SupportedClient', connections.get(DEFAULT_CONNECTION_NAME)) def set_connection(conn: SupportedClient) -> None: diff --git a/src/dipdup/datasources/_subsquid.py b/src/dipdup/datasources/_subsquid.py index 94609dff1..6200dae7d 100644 --- a/src/dipdup/datasources/_subsquid.py +++ b/src/dipdup/datasources/_subsquid.py @@ -30,7 +30,7 @@ async def query(self, query: QueryT) -> list[dict[str, Any]]: url='', json=query, ) - return cast(list[dict[str, Any]], response) + return cast('list[dict[str, Any]]', response) class AbstractSubsquidDatasource( diff --git a/src/dipdup/datasources/coinbase.py b/src/dipdup/datasources/coinbase.py index 346fe1087..f1f508e6f 100644 --- a/src/dipdup/datasources/coinbase.py +++ b/src/dipdup/datasources/coinbase.py @@ -26,7 +26,7 @@ async def run(self) -> None: async def get_oracle_prices(self) -> dict[str, Any]: return cast( - dict[str, Any], + 'dict[str, Any]', await self.request( 'get', url='oracle', diff --git a/src/dipdup/datasources/evm_etherscan.py b/src/dipdup/datasources/evm_etherscan.py index 7f4b278c0..f9209fcbd 100644 --- a/src/dipdup/datasources/evm_etherscan.py +++ b/src/dipdup/datasources/evm_etherscan.py @@ -56,7 +56,7 @@ async def get_abi(self, address: str) -> dict[str, Any] | list[Any]: self._logger.warning('Failed to get ABI: %s', e) try: - return cast(dict[str, Any], orjson.loads(result)) + return cast('dict[str, Any]', orjson.loads(result)) except orjson.JSONDecodeError as e: raise DatasourceError(result, self.name) from e @@ -78,5 +78,5 @@ async def get_abi_failover(self, address: str) -> dict[str, Any]: regex = r'id=["\']js-copytextarea2(.*)>(\[.*?)\<\/pre' if (match := re.search(regex, html)) and (abi := match.group(2)): - return cast(dict[str, Any], orjson.loads(abi)) + return cast('dict[str, Any]', orjson.loads(abi)) raise DatasourceError('Failed to get ABI', self.name) diff --git a/src/dipdup/datasources/substrate_subscan.py b/src/dipdup/datasources/substrate_subscan.py index ba453454a..615edfb6c 100644 --- a/src/dipdup/datasources/substrate_subscan.py +++ b/src/dipdup/datasources/substrate_subscan.py @@ -19,7 +19,7 @@ async def get_runtime_list(self) -> list[dict[str, Any]]: 'post', 'scan/runtime/list', ) - return cast(list[dict[str, Any]], res['data']['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( @@ -27,4 +27,4 @@ async def get_runtime_metadata(self, spec_version: int) -> dict[str, Any]: 'scan/runtime/metadata', json={'spec': spec_version}, ) - return cast(dict[str, Any], res['data']['info']['metadata']) + return cast('dict[str, Any]', res['data']['info']['metadata']) diff --git a/src/dipdup/datasources/tezos_tzkt.py b/src/dipdup/datasources/tezos_tzkt.py index 73df76f53..07e2de1c2 100644 --- a/src/dipdup/datasources/tezos_tzkt.py +++ b/src/dipdup/datasources/tezos_tzkt.py @@ -431,7 +431,7 @@ async def get_contract_summary(self, address: str) -> dict[str, Any]: """Get contract summary""" self._logger.info('Fetching contract summary for address `%s`', address) return cast( - dict[str, Any], + 'dict[str, Any]', await self.request( 'get', url=f'v1/contracts/{address}', @@ -464,7 +464,7 @@ async def get_contract_address(self, code_hash: int, type_hash: int) -> str: ) if not response: raise ValueError(f'Contract with code hash `{code_hash}` not found') from None - address = cast(str, response[0]['address']) + address = cast('str', response[0]['address']) self._contract_hashes.add(address, code_hash, type_hash) return address @@ -472,7 +472,7 @@ async def get_contract_storage(self, address: str) -> dict[str, Any]: """Get contract storage""" self._logger.info('Fetching contract storage for address `%s`', address) return cast( - dict[str, Any], + 'dict[str, Any]', await self.request( 'get', url=f'v1/contracts/{address}/storage', @@ -489,7 +489,7 @@ async def get_jsonschemas(self, address: str) -> dict[str, Any]: else: raise NotImplementedError return cast( - dict[str, Any], + 'dict[str, Any]', await self.request( 'get', url=f'v1/{endpoint}/{address}/interface', @@ -1095,7 +1095,7 @@ async def _subscribe(self, subscription: TezosTzktSubscription) -> None: async def _on_subscribe(message: CompletionMessage) -> None: if message.error: await self._on_error(message) - level = cast(int, message.result) + level = cast('int', message.result) self._subscriptions.set_sync_level(subscription, level) event.set() @@ -1261,17 +1261,17 @@ async def _on_message(self, type_: TezosTzktMessageType, message: list[dict[str, # NOTE: Process extensive data from buffer for buffered_message in self._buffer.yield_from(): if buffered_message.type == TezosTzktMessageType.operation: - await self._process_operations_data(cast(list[dict[str, Any]], buffered_message.data)) + await self._process_operations_data(cast('list[dict[str, Any]]', buffered_message.data)) elif buffered_message.type == TezosTzktMessageType.token_transfer: - await self._process_token_transfers_data(cast(list[dict[str, Any]], buffered_message.data)) + await self._process_token_transfers_data(cast('list[dict[str, Any]]', buffered_message.data)) elif buffered_message.type == TezosTzktMessageType.token_balance: - await self._process_token_balances_data(cast(list[dict[str, Any]], buffered_message.data)) + await self._process_token_balances_data(cast('list[dict[str, Any]]', buffered_message.data)) elif buffered_message.type == TezosTzktMessageType.big_map: - await self._process_big_maps_data(cast(list[dict[str, Any]], buffered_message.data)) + await self._process_big_maps_data(cast('list[dict[str, Any]]', buffered_message.data)) elif buffered_message.type == TezosTzktMessageType.head: - await self._process_head_data(cast(dict[str, Any], buffered_message.data)) + await self._process_head_data(cast('dict[str, Any]', buffered_message.data)) elif buffered_message.type == TezosTzktMessageType.event: - await self._process_events_data(cast(list[dict[str, Any]], buffered_message.data)) + await self._process_events_data(cast('list[dict[str, Any]]', buffered_message.data)) else: raise NotImplementedError(f'Unknown message type: {buffered_message.type}') diff --git a/src/dipdup/datasources/tzip_metadata.py b/src/dipdup/datasources/tzip_metadata.py index 4d2f56504..e787919bd 100644 --- a/src/dipdup/datasources/tzip_metadata.py +++ b/src/dipdup/datasources/tzip_metadata.py @@ -26,7 +26,7 @@ async def get_contract_metadata(self, address: str) -> dict[str, Any] | None: ) response = response['contract_metadata'] if response: - return cast(dict[str, Any], response[0]['metadata']) + return cast('dict[str, Any]', response[0]['metadata']) return None async def get_token_metadata(self, address: str, token_id: int) -> dict[str, Any] | None: @@ -42,7 +42,7 @@ async def get_token_metadata(self, address: str, token_id: int) -> dict[str, Any response = response['token_metadata'] if response: return cast( - dict[str, Any], + 'dict[str, Any]', response[0]['metadata'], ) return None diff --git a/src/dipdup/hasura.py b/src/dipdup/hasura.py index da7444095..2a5a3b406 100644 --- a/src/dipdup/hasura.py +++ b/src/dipdup/hasura.py @@ -209,7 +209,7 @@ async def configure(self, force: bool = False) -> None: def _get_source(self, metadata: dict[str, Any], name: str) -> dict[str, Any] | None: for source in metadata['sources']: if source['name'] == name: - return cast(dict[str, Any], source) + return cast('dict[str, Any]', source) else: return None @@ -242,7 +242,7 @@ async def _hasura_request( if errors := result.get('error') or result.get('errors'): raise HasuraError(errors) - return cast(dict[str, Any], result) + return cast('dict[str, Any]', result) async def _healthcheck(self) -> None: self._logger.info('Connecting to Hasura instance') @@ -476,7 +476,7 @@ async def _get_fields_json(self, name: str) -> list[dict[str, Any]]: }, ) try: - return cast(list[dict[str, Any]], result['data']['__type']['fields']) + return cast('list[dict[str, Any]]', result['data']['__type']['fields']) except TypeError as e: raise HasuraError(f'Unknown table `{name}`') from e diff --git a/src/dipdup/index.py b/src/dipdup/index.py index aecda02ef..90d660416 100644 --- a/src/dipdup/index.py +++ b/src/dipdup/index.py @@ -218,7 +218,7 @@ def get_sync_level(self) -> int: # NOTE: Multiple sync levels means index with new subscriptions was added in runtime. # NOTE: Choose the highest level; outdated realtime messages will be dropped from the queue anyway. - return max(cast(set[int], sync_levels)) + return max(cast('set[int]', sync_levels)) async def initialize_state(self, state: models.Index | None = None) -> None: if self._state: diff --git a/src/dipdup/indexes/tezos_operations/parser.py b/src/dipdup/indexes/tezos_operations/parser.py index 212bb2e23..4b48cacfd 100644 --- a/src/dipdup/indexes/tezos_operations/parser.py +++ b/src/dipdup/indexes/tezos_operations/parser.py @@ -28,7 +28,7 @@ def extract_root_outer_type(storage_type: type[BaseModel]) -> type[BaseModel]: root_field = storage_type.model_fields['root'] if not root_field.is_required(): # NOTE: Optional is a magic _SpecialForm - return cast(type[BaseModel], Optional[root_field.annotation]) # noqa: UP007 + return cast('type[BaseModel]', Optional[root_field.annotation]) # noqa: UP007 return root_field.annotation # type: ignore[return-value] @@ -102,7 +102,7 @@ def _preprocess_bigmap_diffs(diffs: Iterable[dict[str, Any]]) -> dict[int, Itera k: tuple(v) for k, v in groupby( filter(lambda d: d['action'] in ('add_key', 'update_key'), diffs), - lambda d: cast(int, d['bigmap']), + lambda d: cast('int', d['bigmap']), ) } diff --git a/src/dipdup/install.py b/src/dipdup/install.py index f2ae97493..88732e1ce 100755 --- a/src/dipdup/install.py +++ b/src/dipdup/install.py @@ -197,7 +197,7 @@ def install( pipx_packages = env._pipx_packages - python_inter_pipx = cast(str, which('python3.12')) + python_inter_pipx = cast('str', which('python3.12')) if 'pyenv' in python_inter_pipx: python_inter_pipx = ( subprocess.run( diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index b467a1d3c..37fc52914 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -1,5 +1,6 @@ from collections.abc import Callable from typing import TYPE_CHECKING +from typing import Any from dipdup import models from dipdup.context import DipDupContext @@ -11,37 +12,53 @@ _ctx: DipDupContext | None = None -async def _tool_config() -> str: +async def _resource_config() -> str: assert _ctx - # FIXME: strip secrets - return _ctx.config.dump() + secret_keys = {'password', 'api_key', 'secret'} + dump = _ctx.config.dump() + # TODO: More accurate filtering + return '\n'.join( + line if not any(key in line for key in secret_keys) else f'{line.split(":")[0]}: ***' + for line in '\n'.split(dump) + ) -async def _tool_indexes() -> str: - res = '' - for m in await models.Index.all(): - res += f""" -Index name: {m.name} -Type: {m.type} -Status: {m.status} -Current height: {m.level} -""" - return res +async def _resource_metrics() -> dict[str, Any]: + metrics_model = await models.Meta.get_or_none(key='dipdup_metrics') + if metrics_model: + return metrics_model.value + return {} -async def _tool_heads() -> str: - res = '' +async def _resource_heads() -> list[dict[str, Any]]: + res = [] for m in await models.Head.all(): - res += f""" -Datasource name: {m.name} -Current height: {m.level} -Block hash: {m.hash} -Block timestamp: {m.timestamp} -""" - + res.append( + { + 'datasource_name': m.name, + 'level': m.level, + 'hash': m.hash, + 'timestamp': m.timestamp, + 'updated_at': m.updated_at, + } + ) return res +async def _resource_indexes() -> list[dict[str, Any]]: + res = [] + for m in await models.Index.all(): + res.append( + { + 'name': m.name, + 'kind': m.type, + 'status': m.status, + 'height': m.level, + 'updated_at': m.updated_at, + } + ) + + def _create_mcp() -> 'FastMCP': from mcp.server.fastmcp import FastMCP @@ -54,20 +71,33 @@ def _create_mcp() -> 'FastMCP': # NOTE: Internal tools - mcp.tool( + mcp.resource( + uri='dipdup://config', name='Config', - description='Describe the current configuration', - )(_tool_config) + description='Dump the current indexer configuration in YAML format', + mime_type='application/yaml', + )(_resource_config) + + mcp.resource( + uri='dipdup://metrics', + name='Metrics', + description='Show the current indexer metrics', + mime_type='application/json', + )(_resource_metrics) + + mcp.resource( + uri='dipdup://heads', + name='Heads', + description='Show the current datasource head blocks', + mime_type='application/json', + )(_resource_heads) - mcp.tool( + mcp.resource( + uri='dipdup://indexes', name='Indexes', - description='Fetch the current state of the indexer', - )(_tool_indexes) - - mcp.tool( - name='Heads', - description='Fetch the current datasource head blocks', - )(_tool_heads) + description='Show the current indexer state', + mime_type='application/json', + )(_resource_indexes) return mcp @@ -91,6 +121,41 @@ def get_mcp() -> 'FastMCP': return _mcp -def tool(name: str, description: str) -> Callable[..., None]: - assert ' ' not in name, 'Tool name should not contain spaces' - return get_mcp().tool(name=name, description=description) +def tool( + name: str, + description: str, +) -> Callable[..., None]: + assert ' ' not in name, 'Name should not contain spaces' + return get_mcp().tool( + name=name, + description=description, + ) + + +def resource( + self, + uri: str, + *, + name: str | None = None, + description: str | None = None, + mime_type: str | None = None, +) -> Callable[..., None]: + assert ' ' not in name, 'Name should not contain spaces' + return get_mcp().resource( + uri, + name=name, + description=description, + mime_type=mime_type, + ) + + +def prompt( + self, + name: str | None = None, + description: str | None = None, +) -> Callable[..., None]: + assert ' ' not in name, 'Name should not contain spaces' + return get_mcp().prompt( + name=name, + description=description, + ) diff --git a/src/dipdup/models/__init__.py b/src/dipdup/models/__init__.py index 9d573c65c..52ea1bced 100644 --- a/src/dipdup/models/__init__.py +++ b/src/dipdup/models/__init__.py @@ -45,11 +45,10 @@ class IndexType(Enum): - """Enum for `dipdup.models.Index`""" + """Kind of the index""" evm_events = 'evm.events' evm_transactions = 'evm.transactions' - evm_subsquid_traces = 'evm.traces' tezos_big_maps = 'tezos.big_maps' tezos_events = 'tezos.events' tezos_head = 'tezos.head' @@ -312,7 +311,7 @@ class BulkUpdateQuery(TortoiseBulkUpdateQuery): # type: ignore[type-arg] async def _execute_many(self, queries_with_params: list[tuple[str, list[Any]]]) -> int: for model in self._objects: if update := ModelUpdate.from_model( - cast(Model, model), + cast('Model', model), ModelUpdateAction.UPDATE, ): get_pending_updates().append(update) @@ -324,7 +323,7 @@ class BulkCreateQuery(TortoiseBulkCreateQuery): # type: ignore[type-arg] async def _execute_many(self, insert_sql: str, insert_sql_all: str) -> None: for model in self._objects: if update := ModelUpdate.from_model( - cast(Model, model), + cast('Model', model), ModelUpdateAction.INSERT, ): get_pending_updates().append(update) diff --git a/src/dipdup/models/substrate.py b/src/dipdup/models/substrate.py index 36e51d26e..4cb132e47 100644 --- a/src/dipdup/models/substrate.py +++ b/src/dipdup/models/substrate.py @@ -123,7 +123,7 @@ def payload(self) -> PayloadT: else: raise NotImplementedError - return cast(PayloadT, payload) + return cast('PayloadT', payload) @property def level(self) -> int: diff --git a/src/dipdup/package.py b/src/dipdup/package.py index fd5855aec..8e2b400ee 100644 --- a/src/dipdup/package.py +++ b/src/dipdup/package.py @@ -216,4 +216,4 @@ def get_callback(self, kind: str, module: str, name: str) -> Callable[..., Await if not callable(callback): raise ProjectPackageError(f'`{path}.{name}` is not a valid callback') self._callbacks[key] = callback - return cast(Callable[..., Awaitable[None]], callback) + return cast('Callable[..., Awaitable[None]]', callback) diff --git a/src/dipdup/performance.py b/src/dipdup/performance.py index 1cd8848b3..235eb80f3 100644 --- a/src/dipdup/performance.py +++ b/src/dipdup/performance.py @@ -93,7 +93,7 @@ def stats(self) -> dict[str, Any]: stats[name] = {'size': len(plain_cache)} for name, fn_cache in chain(self._lru.items(), self._alru.items()): name = f'lru:{name}' - c = cast(_CacheInfo, fn_cache.cache_info()) # type: ignore[attr-defined] + c = cast('_CacheInfo', fn_cache.cache_info()) # type: ignore[attr-defined] if not c.hits and not c.misses: continue stats[name] = { @@ -117,7 +117,7 @@ def clear(self) -> None: items += len(plain_cache) plain_cache.clear() for fn_cache in chain(self._lru.values(), self._alru.values()): - stats = cast(_CacheInfo, fn_cache.cache_info()) # type: ignore[attr-defined] + stats = cast('_CacheInfo', fn_cache.cache_info()) # type: ignore[attr-defined] items += stats.currsize fn_cache.cache_clear() # type: ignore[attr-defined] for model_cls in self._models: diff --git a/tests/test_index/test_tzkt_operations.py b/tests/test_index/test_tzkt_operations.py index 64e47982e..8e6125744 100644 --- a/tests/test_index/test_tzkt_operations.py +++ b/tests/test_index/test_tzkt_operations.py @@ -33,7 +33,7 @@ async def tzkt() -> AsyncIterator[TezosTzktDatasource]: def index_config() -> TezosOperationsIndexConfig: config = DipDupConfig.load([TEST_CONFIGS / 'operation_filters.yml'], True) config.initialize() - return cast(TezosOperationsIndexConfig, config.indexes['test']) + return cast('TezosOperationsIndexConfig', config.indexes['test']) async def test_ignored_type_filter( @@ -155,7 +155,7 @@ async def test_realtime() -> None: await dipdup._set_up_datasources(stack) dispatcher = dipdup._index_dispatcher - index = cast(TezosOperationsIndex, await spawn_index(dipdup, 'tzbtc_holders_mainnet')) + index = cast('TezosOperationsIndex', await spawn_index(dipdup, 'tzbtc_holders_mainnet')) # NOTE: Start sync and realtime connection simultaneously. first_level = 1365000 diff --git a/uv.lock b/uv.lock index 52faccc29..0fd3355c9 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ requires-python = "==3.12.*" [[package]] name = "aerich" -version = "0.8.1" +version = "0.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asyncclick" }, @@ -11,23 +11,23 @@ dependencies = [ { name = "pydantic" }, { name = "tortoise-orm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4f/41/3c478b7a344abda866f2c644b056ebae638499bbe0eec0010e7361211774/aerich-0.8.1.tar.gz", hash = "sha256:1e95b1c04dfc0c634dd43b0123933038c820140e17a4b27885a63b7461eb0632", size = 31429 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/d1/701a44d8f7fdccf700400ce16a26c679b3f4e36de9fb86c3d9f55f64b07f/aerich-0.8.2.tar.gz", hash = "sha256:0ed2b15bb017785d17323329e5448b4b12fcfec08a20932626c12aa59a8378d6", size = 32464 } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/72/50b961b8f46080c626a566be7769163d593b429a5d94029cc32ef5f1fe4d/aerich-0.8.1-py3-none-any.whl", hash = "sha256:2743cf85bd9957ea173055dad07ee5a3219067e4f117d5402a44204c27e83c9f", size = 42303 }, + { url = "https://files.pythonhosted.org/packages/92/6a/a3f4e906ac8d7fe0af4d41ded66edb6332d9d289011da2033eeab69393d2/aerich-0.8.2-py3-none-any.whl", hash = "sha256:c1000cebd8525a1310a0cf0ea7d9a03cc2529413f182e90f08a6c6f4f61edcda", size = 36611 }, ] [[package]] name = "aiohappyeyeballs" -version = "2.4.6" +version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/07/508f9ebba367fc3370162e53a3cfd12f5652ad79f0e0bfdf9f9847c6f159/aiohappyeyeballs-2.4.6.tar.gz", hash = "sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0", size = 21726 } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4c/03fb05f56551828ec67ceb3665e5dc51638042d204983a03b0a1541475b6/aiohappyeyeballs-2.4.6-py3-none-any.whl", hash = "sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, ] [[package]] name = "aiohttp" -version = "3.11.12" +version = "3.11.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -38,24 +38,24 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/4b/952d49c73084fb790cb5c6ead50848c8e96b4980ad806cf4d2ad341eaa03/aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0", size = 7673175 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/3f/c4a667d184c69667b8f16e0704127efc5f1e60577df429382b4d95fd381e/aiohttp-3.11.13.tar.gz", hash = "sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb", size = 7674284 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/d0/94346961acb476569fca9a644cc6f9a02f97ef75961a6b8d2b35279b8d1f/aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250", size = 704837 }, - { url = "https://files.pythonhosted.org/packages/a9/af/05c503f1cc8f97621f199ef4b8db65fb88b8bc74a26ab2adb74789507ad3/aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1", size = 464218 }, - { url = "https://files.pythonhosted.org/packages/f2/48/b9949eb645b9bd699153a2ec48751b985e352ab3fed9d98c8115de305508/aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c", size = 456166 }, - { url = "https://files.pythonhosted.org/packages/14/fb/980981807baecb6f54bdd38beb1bd271d9a3a786e19a978871584d026dcf/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df", size = 1682528 }, - { url = "https://files.pythonhosted.org/packages/90/cb/77b1445e0a716914e6197b0698b7a3640590da6c692437920c586764d05b/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259", size = 1737154 }, - { url = "https://files.pythonhosted.org/packages/ff/24/d6fb1f4cede9ccbe98e4def6f3ed1e1efcb658871bbf29f4863ec646bf38/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d", size = 1793435 }, - { url = "https://files.pythonhosted.org/packages/17/e2/9f744cee0861af673dc271a3351f59ebd5415928e20080ab85be25641471/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e", size = 1692010 }, - { url = "https://files.pythonhosted.org/packages/90/c4/4a1235c1df544223eb57ba553ce03bc706bdd065e53918767f7fa1ff99e0/aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0", size = 1619481 }, - { url = "https://files.pythonhosted.org/packages/60/70/cf12d402a94a33abda86dd136eb749b14c8eb9fec1e16adc310e25b20033/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0", size = 1641578 }, - { url = "https://files.pythonhosted.org/packages/1b/25/7211973fda1f5e833fcfd98ccb7f9ce4fbfc0074e3e70c0157a751d00db8/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9", size = 1684463 }, - { url = "https://files.pythonhosted.org/packages/93/60/b5905b4d0693f6018b26afa9f2221fefc0dcbd3773fe2dff1a20fb5727f1/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f", size = 1646691 }, - { url = "https://files.pythonhosted.org/packages/b4/fc/ba1b14d6fdcd38df0b7c04640794b3683e949ea10937c8a58c14d697e93f/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9", size = 1702269 }, - { url = "https://files.pythonhosted.org/packages/5e/39/18c13c6f658b2ba9cc1e0c6fb2d02f98fd653ad2addcdf938193d51a9c53/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef", size = 1734782 }, - { url = "https://files.pythonhosted.org/packages/9f/d2/ccc190023020e342419b265861877cd8ffb75bec37b7ddd8521dd2c6deb8/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9", size = 1694740 }, - { url = "https://files.pythonhosted.org/packages/3f/54/186805bcada64ea90ea909311ffedcd74369bfc6e880d39d2473314daa36/aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a", size = 411530 }, - { url = "https://files.pythonhosted.org/packages/3d/63/5eca549d34d141bcd9de50d4e59b913f3641559460c739d5e215693cb54a/aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802", size = 437860 }, + { url = "https://files.pythonhosted.org/packages/9a/a9/6657664a55f78db8767e396cc9723782ed3311eb57704b0a5dacfa731916/aiohttp-3.11.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90", size = 705054 }, + { url = "https://files.pythonhosted.org/packages/3b/06/f7df1fe062d16422f70af5065b76264f40b382605cf7477fa70553a9c9c1/aiohttp-3.11.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d", size = 464440 }, + { url = "https://files.pythonhosted.org/packages/22/3a/8773ea866735754004d9f79e501fe988bdd56cfac7fdecbc8de17fc093eb/aiohttp-3.11.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f", size = 456394 }, + { url = "https://files.pythonhosted.org/packages/7f/61/8e2f2af2327e8e475a2b0890f15ef0bbfd117e321cce1e1ed210df81bbac/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2", size = 1682752 }, + { url = "https://files.pythonhosted.org/packages/24/ed/84fce816bc8da39aa3f6c1196fe26e47065fea882b1a67a808282029c079/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b", size = 1737375 }, + { url = "https://files.pythonhosted.org/packages/d9/de/35a5ba9e3d21ebfda1ebbe66f6cc5cbb4d3ff9bd6a03e5e8a788954f8f27/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb", size = 1793660 }, + { url = "https://files.pythonhosted.org/packages/ff/fe/0f650a8c7c72c8a07edf8ab164786f936668acd71786dd5885fc4b1ca563/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117", size = 1692233 }, + { url = "https://files.pythonhosted.org/packages/a8/20/185378b3483f968c6303aafe1e33b0da0d902db40731b2b2b2680a631131/aiohttp-3.11.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778", size = 1619708 }, + { url = "https://files.pythonhosted.org/packages/a4/f9/d9c181750980b17e1e13e522d7e82a8d08d3d28a2249f99207ef5d8d738f/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d", size = 1641802 }, + { url = "https://files.pythonhosted.org/packages/50/c7/1cb46b72b1788710343b6e59eaab9642bd2422f2d87ede18b1996e0aed8f/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496", size = 1684678 }, + { url = "https://files.pythonhosted.org/packages/71/87/89b979391de840c5d7c34e78e1148cc731b8aafa84b6a51d02f44b4c66e2/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820", size = 1646921 }, + { url = "https://files.pythonhosted.org/packages/a7/db/a463700ac85b72f8cf68093e988538faaf4e865e3150aa165cf80ee29d6e/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a", size = 1702493 }, + { url = "https://files.pythonhosted.org/packages/b8/32/1084e65da3adfb08c7e1b3e94f3e4ded8bd707dee265a412bc377b7cd000/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e", size = 1735004 }, + { url = "https://files.pythonhosted.org/packages/a0/bb/a634cbdd97ce5d05c2054a9a35bfc32792d7e4f69d600ad7e820571d095b/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637", size = 1694964 }, + { url = "https://files.pythonhosted.org/packages/fd/cf/7d29db4e5c28ec316e5d2ac9ac9df0e2e278e9ea910e5c4205b9b64c2c42/aiohttp-3.11.13-cp312-cp312-win32.whl", hash = "sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee", size = 411746 }, + { url = "https://files.pythonhosted.org/packages/65/a9/13e69ad4fd62104ebd94617f9f2be58231b50bb1e6bac114f024303ac23b/aiohttp-3.11.13-cp312-cp312-win_amd64.whl", hash = "sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8", size = 438078 }, ] [[package]] @@ -162,11 +162,11 @@ wheels = [ [[package]] name = "argcomplete" -version = "3.5.3" +version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/be/6c23d80cb966fb8f83fb1ebfb988351ae6b0554d0c3a613ee4531c026597/argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392", size = 72999 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/be/29abccb5d9f61a92886a2fba2ac22bf74326b5c4f55d36d0a56094630589/argcomplete-3.6.0.tar.gz", hash = "sha256:2e4e42ec0ba2fff54b0d244d0b1623e86057673e57bafe72dda59c64bd5dee8b", size = 73135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/08/2a4db06ec3d203124c967fc89295e85a202e5cbbcdc08fd6a64b65217d1e/argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61", size = 43569 }, + { url = "https://files.pythonhosted.org/packages/08/94/e786d91ccc3a1fc664c20332825b73da20928eb067cdc984b821948a1acc/argcomplete-3.6.0-py3-none-any.whl", hash = "sha256:4e3e4e10beb20e06444dbac0ac8dda650cb6349caeefe980208d3c548708bedd", size = 43769 }, ] [[package]] @@ -219,11 +219,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, ] [[package]] @@ -246,25 +246,24 @@ wheels = [ [[package]] name = "bitarray" -version = "3.0.0" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/62/dcfac53d22ef7e904ed10a8e710a36391d2d6753c34c869b51bfc5e4ad54/bitarray-3.0.0.tar.gz", hash = "sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03", size = 126627 } +sdist = { url = "https://files.pythonhosted.org/packages/41/9a/19f3d74ed2949afcc5c4f2ae6aec2e962004b8a9855070f68b3c6d7e838b/bitarray-3.1.1.tar.gz", hash = "sha256:a3c1d74ac2c969bac33169286fe601f8a6f4ca0e8f26dbaa22ad61fbf8fcf259", size = 135976 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/2e/2e4beb2b714dc83a9e90ac0e4bacb1a191c71125734f72962ee2a20b9cfb/bitarray-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d", size = 172152 }, - { url = "https://files.pythonhosted.org/packages/e0/1f/9ec96408c060ffc3df5ba64d2b520fd0484cb3393a96691df8f660a43b17/bitarray-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9", size = 123319 }, - { url = "https://files.pythonhosted.org/packages/80/9f/4dd05086308bfcc84ad88c663460a8ad9f5f638f9f96eb5fa08381054db6/bitarray-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942", size = 121242 }, - { url = "https://files.pythonhosted.org/packages/55/bb/8865b7380e9d20445bc775079f24f2279a8c0d9ee11d57c49b118d39beaf/bitarray-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b", size = 287463 }, - { url = "https://files.pythonhosted.org/packages/db/8b/779119ee438090a80cbfaa49f96e783651183ab4c25b9760fe360aa7cb31/bitarray-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4", size = 301599 }, - { url = "https://files.pythonhosted.org/packages/41/25/78f7ba7fa8ab428767dfb722fc1ea9aac4a9813e348023d8047d8fd32253/bitarray-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc", size = 304837 }, - { url = "https://files.pythonhosted.org/packages/f7/8d/30a448d3157b4239e635c92fc3b3789a5b87784875ca2776f65bd543d136/bitarray-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755", size = 288588 }, - { url = "https://files.pythonhosted.org/packages/86/e0/c1f1b595682244f55119d55f280b5a996bcd462b702ec220d976a7566d27/bitarray-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b", size = 279002 }, - { url = "https://files.pythonhosted.org/packages/5c/4d/a17626923ad2c9d20ed1625fc5b27a8dfe2d1a3e877083e9422455ec302d/bitarray-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80", size = 281898 }, - { url = "https://files.pythonhosted.org/packages/50/d8/5c410580a510e669d9a28bf17675e58843236c55c60fc6dc8f8747808757/bitarray-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1", size = 274622 }, - { url = "https://files.pythonhosted.org/packages/e7/21/de2e8eda85c5f6a05bda75a00c22c94aee71ef09db0d5cbf22446de74312/bitarray-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3", size = 296930 }, - { url = "https://files.pythonhosted.org/packages/13/7b/7cfad12d77db2932fb745fa281693b0031c3dfd7f2ecf5803be688cc3798/bitarray-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f", size = 309836 }, - { url = "https://files.pythonhosted.org/packages/53/e1/5120fbb8438a0d718e063f70168a2975e03f00ce6b86e74b8eec079cb492/bitarray-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a", size = 281535 }, - { url = "https://files.pythonhosted.org/packages/73/75/8acebbbb4f85dcca73b8e91dde5d3e1e3e2317b36fae4f5b133c60720834/bitarray-3.0.0-cp312-cp312-win32.whl", hash = "sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c", size = 114423 }, - { url = "https://files.pythonhosted.org/packages/ca/56/dadae4d4351b337de6e0269001fb40f3ebe9f72222190456713d2c1be53d/bitarray-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999", size = 121680 }, + { url = "https://files.pythonhosted.org/packages/29/e8/b24ad217de77f89b045c994159d9411d18aa6e900b450186160c8d3c51c8/bitarray-3.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1af43aa945c595bff4386a2b64c46f6cee653883e295907e419f6745123a168f", size = 132304 }, + { url = "https://files.pythonhosted.org/packages/f4/3a/d690bf045027bf68008698ad05622a95bebb9744a4f03b65b7ccc8e27161/bitarray-3.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e43e1a8b38d6e8ccc27cad74dabd8005b078856c471a1d0537491b71184f209", size = 129192 }, + { url = "https://files.pythonhosted.org/packages/22/8d/27917c589b2abd4e7d9625e809ecb94d58a7bab795980aad71ccb9e2ec37/bitarray-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3816df3db86feaa8b4efd6844c6808147954626898355253e11e2544145f48", size = 302728 }, + { url = "https://files.pythonhosted.org/packages/af/b2/8853417c300ed2deba433370adcbcc7782d127fb8689b81900ef30fefc01/bitarray-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f913548d2907633782f0c3de72fa28646e1f97bdaf7f2df04014a17e923a258b", size = 316634 }, + { url = "https://files.pythonhosted.org/packages/ac/27/4e73989d43b3fa50846aba2786dce9e287bad50d51bf10626ba54f126d82/bitarray-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aceaaff83bcdf87bcc13788e29fd068d1a1e6965946ffed80f6e9323e5edd3d", size = 318709 }, + { url = "https://files.pythonhosted.org/packages/89/b8/5a3ba6c0c7bc5c45ded73d346cf9e9ebd6724c5afdc741a4255a7531e718/bitarray-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:079a2fd318d2300c2ef6e84fe9f8f563bcfad46b1360b7d58d82711e5bd8ea56", size = 303590 }, + { url = "https://files.pythonhosted.org/packages/03/45/f28008eea33a8dceaab3887d3085402f7a6aa8db440b93e7789099d7ca20/bitarray-3.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41f0f93a3ccde6f0e3b953d5adc89f7d4acdd3aadf71af8353c8b288a9d4bd80", size = 293191 }, + { url = "https://files.pythonhosted.org/packages/7a/d8/97e13bfcfd89232a89880aced20548672fbb64a1eb652aab26a7e786d264/bitarray-3.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51f860b200fa90e446db77fe57173603ddb81eef4d4ba1ccdc277b564b3f20ab", size = 296698 }, + { url = "https://files.pythonhosted.org/packages/8f/1a/bae81c46ebddf5024e8eabe894d76c4095a8bea6d868edfdb6a0fc207f17/bitarray-3.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5c819daa6c11107614328d9018ccbaf5b7c64060172bf564373d62928f139e87", size = 288895 }, + { url = "https://files.pythonhosted.org/packages/be/ef/e2984fc89c94a05d8d27647ae734e360d85f9fac042d7ea18547f28ac64e/bitarray-3.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:21081fb49d6b0a19b238d30dc0c948bec365bf16fccc2a1478a2794b37a6a812", size = 311913 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/032b6e09d39e80a9df1ca644bfb6b8a0dbdfd68c12c4195e060797ea19d2/bitarray-3.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:514757d0f76d9f84187ea15debd8b3956438f6f4f1e600be1019facdb6076796", size = 323415 }, + { url = "https://files.pythonhosted.org/packages/40/fe/ec3ce002540644f2c89ea6109138d3281336f2d11af33dd77729314bf93e/bitarray-3.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a44c5271f7e079530f7a5eecac55cb4ff350ac82db768c550f66c1419893e4c7", size = 295773 }, + { url = "https://files.pythonhosted.org/packages/ea/aa/de763b168406eb23a8f489006905dd51c9ee3965f5220170b74097662d8f/bitarray-3.1.1-cp312-cp312-win32.whl", hash = "sha256:97825938fa3fd7e917e40d66ab70ff133dee385da13cf91e0bd5cd4ec2d97048", size = 127695 }, + { url = "https://files.pythonhosted.org/packages/fa/a1/3331f8b4c422f30839fee1f5e486524a7f82269d46008af84ff300b39084/bitarray-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:1dffa70fdf6e4aba4e4f8c8aa408950c3199f71546fb24cafc70f03400b22a59", size = 134144 }, ] [[package]] @@ -329,19 +328,19 @@ wheels = [ [[package]] name = "ckzg" -version = "2.0.1" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/80/4b6219a65634915efc4694fa606f38d4b893dcdc1e50b9bcf69b38ec82b0/ckzg-2.0.1.tar.gz", hash = "sha256:62c5adc381637affa7e1df465c57750b356a761b8a3164c3106589b02532b9c9", size = 1113747 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/09/f6c82fe66a577f83939e5bd729dc7ace3e84a3431c657f0f49fe233fd232/ckzg-2.1.0.tar.gz", hash = "sha256:73a353f31c945f0617a6b98d82fbb23909ac5039a10e345c681b728dd917b51a", size = 1120597 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/87/dcc62fc2f6651127b6306a37db492998c291ad1a09a6a0d18895882fec51/ckzg-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:285cf3121b8a8c5609c5b706314f68d2ba2784ab02c5bb7487c6ae1714ecb27f", size = 114776 }, - { url = "https://files.pythonhosted.org/packages/fd/99/2d3aa09ebf692c26e03d17b9e7426a34fd71fe4d9b2ff1acf482736cc8da/ckzg-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f927bc41c2551b0ef0056a649a7ebed29d9665680a10795f4cee5002c69ddb7", size = 98711 }, - { url = "https://files.pythonhosted.org/packages/50/b3/44a533895aa4257d0dcb2818f7dd9b1321664784cac2d381022ed8c40113/ckzg-2.0.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd9fb690c88919f30c9f3ab7cc46a7ecd734d5ff4c9ccea383c119b9b7cc4da", size = 175026 }, - { url = "https://files.pythonhosted.org/packages/54/a2/c594861665851f91ae81ec29cf90e38999de042aa95604737d4b779a8609/ckzg-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fabc3bd41b306d1c7025d561c3281a007c2aca8ceaf998582dc3894904d9c73e", size = 161039 }, - { url = "https://files.pythonhosted.org/packages/59/a0/96bb77fb8bf4cd4d51d8bd1d67d59d13f51fa2477b11b915ab6465aa92ce/ckzg-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb50c53efdb9c34f762bd0c8006cf79bc92a9daf47aa6b541e496988484124f", size = 169889 }, - { url = "https://files.pythonhosted.org/packages/68/c4/77d54a7e5f85d833e9664935f6278fbea7de30f4fde213d121f7fdbc27a0/ckzg-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7960cc62f959403293fb53a3c2404778369ae7cefc6d7f202e5e00567cf98c4b", size = 171378 }, - { url = "https://files.pythonhosted.org/packages/02/54/6520ab37c06680910f8ff99afdc473c945c37ab1016662288d98a028d775/ckzg-2.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d721bcd492294c70eca39da0b0a433c29b6a571dbac2f7084bab06334904af06", size = 185969 }, - { url = "https://files.pythonhosted.org/packages/d6/fa/16c3a4fd8353a3a9f95728f4141b2800b08e588522f7b5644c91308f6fe1/ckzg-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dde2391d025b5033ef0eeacf62b11ecfe446aea25682b5f547a907766ad0a8cb", size = 180093 }, - { url = "https://files.pythonhosted.org/packages/d5/ae/91d36445c247a8832bbb7a71bd75293c4c006731d03a2ccaa13e5506ac8a/ckzg-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fab8859d9420f6f7df4e094ee3639bc49d18c8dab0df81bee825e2363dd67a09", size = 98280 }, + { url = "https://files.pythonhosted.org/packages/20/ac/d28074b2bf8767bb872f5ea964bd5f20cff2f9aaf68c01daee86ea28135b/ckzg-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0959685678e3b89d740412f6d7ae0821b74ccbeac04080cb066774ea3044e2e9", size = 116255 }, + { url = "https://files.pythonhosted.org/packages/a6/82/83a44447ab4c15eff1a324a0d458878cee3e08f6870c4e37213e8f656cc0/ckzg-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1bc4c46b0d7db4dd88b55cbd40d13e193536dcd5ae5d0634f0d838de832f429e", size = 100050 }, + { url = "https://files.pythonhosted.org/packages/c9/a0/20b76eaebe3e431432664025a8bcaeecae7de86434f2dc9c96c4d1eaa480/ckzg-2.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a4a3de4f0e264c6d1676379a8968aef681f14df7b1144b7a9ba391180f33510", size = 175595 }, + { url = "https://files.pythonhosted.org/packages/ef/16/6a7ef181ac87373793179d276ecaec094cc244798ffebc4a72ef4ba4500c/ckzg-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d9b35cf921a68769dce6fb64c34a7c331e6f7b8055bfbb8661e7163180f2742", size = 161720 }, + { url = "https://files.pythonhosted.org/packages/e7/a7/d5afdbc5d1eebddc814872c4fc62ada407845c92654628b459a7eeb5d721/ckzg-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1054189f4c6b83d19e2c1a65521485227eb3b63fa3211adccaa7c587befc2a", size = 170587 }, + { url = "https://files.pythonhosted.org/packages/5d/bd/5c88328f30cbd1421a32e584f9277dd0b88e76f8e61f07b8b2a99a1f051d/ckzg-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cecfafea88ef8106440e22eb6db56bf702f30d3af8a3fb54b2a628a5c4e10056", size = 173593 }, + { url = "https://files.pythonhosted.org/packages/df/8a/342a365da21349709a7eaf49b768f1eee7cce570e1a9bb61f4f4c8c4213a/ckzg-2.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a13ce05bc4d2b93aa4f8aaecf9d785878f3d319a05e499268035a1ab1d464d52", size = 188496 }, + { url = "https://files.pythonhosted.org/packages/21/0a/0e9af80d3bed5fe4c7b83da2de1defb2a94e2913c7aba8f104ae4b1ac5ed/ckzg-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:516610ac84f414338b0c1dc41a423906503e34b6672450e50cf22a21a707e51f", size = 183480 }, + { url = "https://files.pythonhosted.org/packages/95/46/c6d2877a2c7137654204cb344d278858035cbf2b2fa29d666f6c48bcd843/ckzg-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:e28a995e3b2923b05adb023412943dfd3b1aa1ca4e3a93d2149dcfbc15de639f", size = 98787 }, ] [[package]] @@ -446,7 +445,7 @@ wheels = [ [[package]] name = "datamodel-code-generator" -version = "0.28.1" +version = "0.28.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -459,9 +458,9 @@ dependencies = [ { name = "pydantic" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/d3/80f6a2394bbf3b46b150fc75afa5b0050f91baa5771e9be87df148013d83/datamodel_code_generator-0.28.1.tar.gz", hash = "sha256:37ef5f3b488f7d7a3f0b5b3ba0f2bc1ae01bab4dc7e0f6b99ff6c40713a6beb3", size = 434901 } +sdist = { url = "https://files.pythonhosted.org/packages/81/84/cfc295394c3114658bfa024279497db4d9e5cb3560681dd018385781e802/datamodel_code_generator-0.28.4.tar.gz", hash = "sha256:ae1056643cf3cdd298ef4b622085158ec7ef1722f6da1c20e7022c942f2e3ebb", size = 441625 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/17/2876ca0a4ac7dd7cb5f56a2f0f6d9ac910969f467e8142c847c45a76b897/datamodel_code_generator-0.28.1-py3-none-any.whl", hash = "sha256:1ff8a56f9550a82bcba3e1ad7ebdb89bc655eeabbc4bc6acfb05977cbdc6381c", size = 115601 }, + { url = "https://files.pythonhosted.org/packages/84/1b/81822753798d29a2f5afa4bde7e7ab29789230e9c11426ab1620758ceef2/datamodel_code_generator-0.28.4-py3-none-any.whl", hash = "sha256:16a58933e7c2de692c88e0061ba9a7e23009fe298a726cbba027574fff25c9ba", size = 116264 }, ] [[package]] @@ -730,14 +729,14 @@ wheels = [ [[package]] name = "eth-typing" -version = "5.1.0" +version = "5.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/6f/ecd98de0b67eefc68e17f6979433534a63e11aac88adaae7dede0b694567/eth_typing-5.1.0.tar.gz", hash = "sha256:8581f212ee6252aaa285377a77620f6e5f6e16ac3f144c61f098fafd47967b1a", size = 21727 } +sdist = { url = "https://files.pythonhosted.org/packages/bd/4a/1faf216a53427ae9111524ebd139b99c0b93fb3dcadf0c972c3433055370/eth_typing-5.2.0.tar.gz", hash = "sha256:28685f7e2270ea0d209b75bdef76d8ecef27703e1a16399f6929820d05071c28", size = 21794 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/2e/40e7577866f4378fb9737e0cb08e3e96e5a25b53821b0139dbfbd77dd66e/eth_typing-5.1.0-py3-none-any.whl", hash = "sha256:c0d6b93f5385aa84efc4b47ae2bd478da069bc0ffda8b67e0ccb573f43defd29", size = 19116 }, + { url = "https://files.pythonhosted.org/packages/3c/83/7b29c2cfde5d2131eac18c47692440465a61122ce28164b5f480c800e358/eth_typing-5.2.0-py3-none-any.whl", hash = "sha256:e1f424e97990fc3c6a1c05a7b0968caed4e20e9c99a4d5f4db3df418e25ddc80", size = 19120 }, ] [[package]] @@ -899,23 +898,23 @@ wheels = [ [[package]] name = "isort" -version = "6.0.0" +version = "6.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/28/b382d1656ac0ee4cef4bf579b13f9c6c813bff8a5cb5996669592c8c75fa/isort-6.0.0.tar.gz", hash = "sha256:75d9d8a1438a9432a7d7b54f2d3b45cad9a4a0fdba43617d9873379704a8bdf1", size = 828356 } +sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c7/d6017f09ae5b1206fbe531f7af3b6dac1f67aedcbd2e79f3b386c27955d6/isort-6.0.0-py3-none-any.whl", hash = "sha256:567954102bb47bb12e0fae62606570faacddd441e45683968c8d1734fb1af892", size = 94053 }, + { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 }, ] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] @@ -1018,7 +1017,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.2.1" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1030,9 +1029,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/30/51e4555826126e3954fa2ab1e934bf74163c5fe05e98f38ca4d0f8abbf63/mcp-1.2.1.tar.gz", hash = "sha256:c9d43dbfe943aa1530e2be8f54b73af3ebfb071243827b4483d421684806cb45", size = 103968 } +sdist = { url = "https://files.pythonhosted.org/packages/50/cc/5c5bb19f1a0f8f89a95e25cb608b0b07009e81fd4b031e519335404e1422/mcp-1.4.1.tar.gz", hash = "sha256:b9655d2de6313f9d55a7d1df62b3c3fe27a530100cc85bf23729145b0dba4c7a", size = 154942 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/0d/6770742a84c8aa1d36c0d628896a380584c5759612e66af7446af07d8775/mcp-1.2.1-py3-none-any.whl", hash = "sha256:579bf9c9157850ebb1344f3ca6f7a3021b0123c44c9f089ef577a7062522f0fd", size = 66453 }, + { url = "https://files.pythonhosted.org/packages/e8/0e/885f156ade60108e67bf044fada5269da68e29d758a10b0c513f4d85dd76/mcp-1.4.1-py3-none-any.whl", hash = "sha256:a7716b1ec1c054e76f49806f7d96113b99fc1166fc9244c2c6f19867cb75b593", size = 72448 }, ] [[package]] @@ -1261,27 +1260,27 @@ wheels = [ [[package]] name = "propcache" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, - { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, - { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, - { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, - { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, - { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, - { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, - { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, - { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, - { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, - { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, - { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, - { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, - { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, - { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, - { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, - { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/76/f941e63d55c0293ff7829dd21e7cf1147e90a526756869a9070f287a68c9/propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5", size = 42722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/2c/921f15dc365796ec23975b322b0078eae72995c7b4d49eba554c6a308d70/propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e", size = 79867 }, + { url = "https://files.pythonhosted.org/packages/11/a5/4a6cc1a559d1f2fb57ea22edc4245158cdffae92f7f92afcee2913f84417/propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af", size = 46109 }, + { url = "https://files.pythonhosted.org/packages/e1/6d/28bfd3af3a567ad7d667348e7f46a520bda958229c4d545ba138a044232f/propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5", size = 45635 }, + { url = "https://files.pythonhosted.org/packages/73/20/d75b42eaffe5075eac2f4e168f6393d21c664c91225288811d85451b2578/propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b", size = 242159 }, + { url = "https://files.pythonhosted.org/packages/a5/fb/4b537dd92f9fd4be68042ec51c9d23885ca5fafe51ec24c58d9401034e5f/propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667", size = 248163 }, + { url = "https://files.pythonhosted.org/packages/e7/af/8a9db04ac596d531ca0ef7dde518feaadfcdabef7b17d6a5ec59ee3effc2/propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7", size = 248794 }, + { url = "https://files.pythonhosted.org/packages/9d/c4/ecfc988879c0fd9db03228725b662d76cf484b6b46f7e92fee94e4b52490/propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7", size = 243912 }, + { url = "https://files.pythonhosted.org/packages/04/a2/298dd27184faa8b7d91cc43488b578db218b3cc85b54d912ed27b8c5597a/propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf", size = 229402 }, + { url = "https://files.pythonhosted.org/packages/be/0d/efe7fec316ca92dbf4bc4a9ba49ca889c43ca6d48ab1d6fa99fc94e5bb98/propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138", size = 226896 }, + { url = "https://files.pythonhosted.org/packages/60/63/72404380ae1d9c96d96e165aa02c66c2aae6072d067fc4713da5cde96762/propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86", size = 221447 }, + { url = "https://files.pythonhosted.org/packages/9d/18/b8392cab6e0964b67a30a8f4dadeaff64dc7022b5a34bb1d004ea99646f4/propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d", size = 222440 }, + { url = "https://files.pythonhosted.org/packages/6f/be/105d9ceda0f97eff8c06bac1673448b2db2a497444de3646464d3f5dc881/propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e", size = 234104 }, + { url = "https://files.pythonhosted.org/packages/cb/c9/f09a4ec394cfcce4053d8b2a04d622b5f22d21ba9bb70edd0cad061fa77b/propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64", size = 239086 }, + { url = "https://files.pythonhosted.org/packages/ea/aa/96f7f9ed6def82db67c972bdb7bd9f28b95d7d98f7e2abaf144c284bf609/propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c", size = 230991 }, + { url = "https://files.pythonhosted.org/packages/5a/11/bee5439de1307d06fad176f7143fec906e499c33d7aff863ea8428b8e98b/propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d", size = 40337 }, + { url = "https://files.pythonhosted.org/packages/e4/17/e5789a54a0455a61cb9efc4ca6071829d992220c2998a27c59aeba749f6f/propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57", size = 44404 }, + { url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101 }, ] [[package]] @@ -1315,20 +1314,22 @@ wheels = [ [[package]] name = "pycryptodome" -version = "3.21.0" +version = "3.22.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/52/13b9db4a913eee948152a079fe58d035bd3d1a519584155da8e786f767e6/pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297", size = 4818071 } +sdist = { url = "https://files.pythonhosted.org/packages/44/e6/099310419df5ada522ff34ffc2f1a48a11b37fc6a76f51a6854c182dbd3e/pycryptodome-3.22.0.tar.gz", hash = "sha256:fd7ab568b3ad7b77c908d7c3f7e167ec5a8f035c64ff74f10d47a4edd043d723", size = 4917300 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/88/5e83de10450027c96c79dc65ac45e9d0d7a7fef334f39d3789a191f33602/pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4", size = 2495937 }, - { url = "https://files.pythonhosted.org/packages/66/e1/8f28cd8cf7f7563319819d1e172879ccce2333781ae38da61c28fe22d6ff/pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b", size = 1634629 }, - { url = "https://files.pythonhosted.org/packages/6a/c1/f75a1aaff0c20c11df8dc8e2bf8057e7f73296af7dfd8cbb40077d1c930d/pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e", size = 2168708 }, - { url = "https://files.pythonhosted.org/packages/ea/66/6f2b7ddb457b19f73b82053ecc83ba768680609d56dd457dbc7e902c41aa/pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8", size = 2254555 }, - { url = "https://files.pythonhosted.org/packages/2c/2b/152c330732a887a86cbf591ed69bd1b489439b5464806adb270f169ec139/pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1", size = 2294143 }, - { url = "https://files.pythonhosted.org/packages/55/92/517c5c498c2980c1b6d6b9965dffbe31f3cd7f20f40d00ec4069559c5902/pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a", size = 2160509 }, - { url = "https://files.pythonhosted.org/packages/39/1f/c74288f54d80a20a78da87df1818c6464ac1041d10988bb7d982c4153fbc/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2", size = 2329480 }, - { url = "https://files.pythonhosted.org/packages/39/1b/d0b013bf7d1af7cf0a6a4fce13f5fe5813ab225313755367b36e714a63f8/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93", size = 2254397 }, - { url = "https://files.pythonhosted.org/packages/14/71/4cbd3870d3e926c34706f705d6793159ac49d9a213e3ababcdade5864663/pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764", size = 1775641 }, - { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, + { url = "https://files.pythonhosted.org/packages/1f/65/a05831c3e4bcd1bf6c2a034e399f74b3d6f30bb4e37e36b9c310c09dc8c0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:009e1c80eea42401a5bd5983c4bab8d516aef22e014a4705622e24e6d9d703c6", size = 2490637 }, + { url = "https://files.pythonhosted.org/packages/5c/76/ff3c2e7a60d17c080c4c6120ebaf60f38717cd387e77f84da4dcf7f64ff0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3b76fa80daeff9519d7e9f6d9e40708f2fce36b9295a847f00624a08293f4f00", size = 1635372 }, + { url = "https://files.pythonhosted.org/packages/cc/7f/cc5d6da0dbc36acd978d80a72b228e33aadaec9c4f91c93221166d8bdc05/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31fa5914b255ab62aac9265654292ce0404f6b66540a065f538466474baedbc", size = 2177456 }, + { url = "https://files.pythonhosted.org/packages/92/65/35f5063e68790602d892ad36e35ac723147232a9084d1999630045c34593/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0092fd476701eeeb04df5cc509d8b739fa381583cda6a46ff0a60639b7cd70d", size = 2263744 }, + { url = "https://files.pythonhosted.org/packages/cc/67/46acdd35b1081c3dbc72dc466b1b95b80d2f64cad3520f994a9b6c5c7d00/pycryptodome-3.22.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d5b0ddc7cf69231736d778bd3ae2b3efb681ae33b64b0c92fb4626bb48bb89", size = 2303356 }, + { url = "https://files.pythonhosted.org/packages/3d/f9/a4f8a83384626098e3f55664519bec113002b9ef751887086ae63a53135a/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f6cf6aa36fcf463e622d2165a5ad9963b2762bebae2f632d719dfb8544903cf5", size = 2176714 }, + { url = "https://files.pythonhosted.org/packages/88/65/e5f8c3a885f70a6e05c84844cd5542120576f4369158946e8cfc623a464d/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:aec7b40a7ea5af7c40f8837adf20a137d5e11a6eb202cde7e588a48fb2d871a8", size = 2337329 }, + { url = "https://files.pythonhosted.org/packages/b8/2a/25e0be2b509c28375c7f75c7e8d8d060773f2cce4856a1654276e3202339/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d21c1eda2f42211f18a25db4eaf8056c94a8563cd39da3683f89fe0d881fb772", size = 2262255 }, + { url = "https://files.pythonhosted.org/packages/41/58/60917bc4bbd91712e53ce04daf237a74a0ad731383a01288130672994328/pycryptodome-3.22.0-cp37-abi3-win32.whl", hash = "sha256:f02baa9f5e35934c6e8dcec91fcde96612bdefef6e442813b8ea34e82c84bbfb", size = 1763403 }, + { url = "https://files.pythonhosted.org/packages/55/f4/244c621afcf7867e23f63cfd7a9630f14cfe946c9be7e566af6c3915bcde/pycryptodome-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:d086aed307e96d40c23c42418cbbca22ecc0ab4a8a0e24f87932eeab26c08627", size = 1794568 }, + { url = "https://files.pythonhosted.org/packages/cd/13/16d3a83b07f949a686f6cfd7cfc60e57a769ff502151ea140ad67b118e26/pycryptodome-3.22.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:98fd9da809d5675f3a65dcd9ed384b9dc67edab6a4cda150c5870a8122ec961d", size = 1700779 }, + { url = "https://files.pythonhosted.org/packages/13/af/16d26f7dfc5fd7696ea2c91448f937b51b55312b5bed44f777563e32a4fe/pycryptodome-3.22.0-pp27-pypy_73-win32.whl", hash = "sha256:37ddcd18284e6b36b0a71ea495a4c4dca35bb09ccc9bfd5b91bfaf2321f131c1", size = 1775230 }, ] [[package]] @@ -1372,15 +1373,15 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.7.1" +version = "2.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } +sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, + { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, ] [[package]] @@ -1412,7 +1413,7 @@ wheels = [ [[package]] name = "pysignalr" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1420,14 +1421,14 @@ dependencies = [ { name = "orjson" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/9f/de6401378e236d77723d743b91bc39bffae6d8ab89689b97991363c94643/pysignalr-1.1.0.tar.gz", hash = "sha256:f9e9ed611c999043778ebb9e5bd7d92e0afae3f9555eb73d447335e48de3b385", size = 16385 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/97/53987ba4f84ca8d91215586202edb354636fb2c29cb2035e3915b84850e1/pysignalr-1.2.0.tar.gz", hash = "sha256:68ad9fb2e1ae76673537e4b35fd88317ed6585ec664756177005896d19d9967b", size = 16767 } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/1f/6df95e7ad27dae9a78c21774e75d7037e8630728849c77426d59a1e688c4/pysignalr-1.1.0-py3-none-any.whl", hash = "sha256:17977720e3360403b05e8c764a28c70a3865fef973cc0f08bd877101f06ae815", size = 19213 }, + { url = "https://files.pythonhosted.org/packages/14/1b/4c3b81738c20ffb36a649f6d17294c6c01d847b644d17e8efc1096a6fcb2/pysignalr-1.2.0-py3-none-any.whl", hash = "sha256:e7ed0c4cb54523cf0c33a0a63ce8422f94b6cbac88f1cae597fe40770c7cbbe9", size = 19936 }, ] [[package]] name = "pytest" -version = "8.3.4" +version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1435,9 +1436,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] [[package]] @@ -1618,6 +1619,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/fb/e4c0ced9893b84ac95b7181d69a9786ce5879aeb3bbbcbba80a164f85d6a/rlp-4.1.0-py3-none-any.whl", hash = "sha256:8eca394c579bad34ee0b937aecb96a57052ff3716e19c7a578883e767bc5da6f", size = 19973 }, ] +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, +] + [[package]] name = "ruamel-yaml" version = "0.18.10" @@ -1649,27 +1659,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 }, - { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 }, - { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 }, - { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 }, - { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 }, - { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 }, - { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 }, - { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 }, - { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 }, - { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 }, - { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 }, - { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 }, - { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 }, - { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 }, - { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 }, - { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 }, - { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 }, +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, + { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, + { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, + { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, + { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, + { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, + { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, + { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, + { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, + { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, + { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, + { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, + { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, + { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, + { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, + { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, + { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, ] [[package]] @@ -1710,15 +1720,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.21.0" +version = "2.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/63/3f0e88709cf4af992e2813c27d8ba628a891db0805e3fcc6dc834e142c5b/sentry_sdk-2.21.0.tar.gz", hash = "sha256:a6d38e0fb35edda191acf80b188ec713c863aaa5ad8d5798decb8671d02077b6", size = 301965 } +sdist = { url = "https://files.pythonhosted.org/packages/81/b6/662988ecd2345bf6c3a5c306a9a3590852742eff91d0a78a143398b816f3/sentry_sdk-2.22.0.tar.gz", hash = "sha256:b4bf43bb38f547c84b2eadcefbe389b36ef75f3f38253d7a74d6b928c07ae944", size = 303539 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/18/7587660cb5e4d07134913d8e74137efcd4903fda873bf612c30eb34c7ab4/sentry_sdk-2.21.0-py2.py3-none-any.whl", hash = "sha256:7623cfa9e2c8150948a81ca253b8e2bfe4ce0b96ab12f8cd78e3ac9c490fd92f", size = 324096 }, + { url = "https://files.pythonhosted.org/packages/12/7f/0e4459173e9671ba5f75a48dda2442bcc48a12c79e54e5789381c8c6a9bc/sentry_sdk-2.22.0-py2.py3-none-any.whl", hash = "sha256:3d791d631a6c97aad4da7074081a57073126c69487560c6f8bffcf586461de66", size = 325815 }, ] [[package]] @@ -1750,7 +1760,7 @@ wheels = [ [[package]] name = "sphinx" -version = "8.1.3" +version = "8.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, @@ -1762,6 +1772,7 @@ dependencies = [ { name = "packaging" }, { name = "pygments" }, { name = "requests" }, + { name = "roman-numerals-py" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, @@ -1770,9 +1781,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 }, ] [[package]] @@ -1900,14 +1911,14 @@ sdist = { url = "https://files.pythonhosted.org/packages/36/ea/2ec249b38dccd8391 [[package]] name = "starlette" -version = "0.45.3" +version = "0.46.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } +sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, + { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, ] [[package]] @@ -1993,14 +2004,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.0.20241016" +version = "2.32.0.20250306" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 } +sdist = { url = "https://files.pythonhosted.org/packages/09/1a/beaeff79ef9efd186566ba5f0d95b44ae21f6d31e9413bcfbef3489b6ae3/types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1", size = 23012 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 }, + { url = "https://files.pythonhosted.org/packages/99/26/645d89f56004aa0ba3b96fec27793e3c7e62b40982ee069e52568922b6db/types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b", size = 20673 }, ] [[package]] @@ -2045,14 +2056,14 @@ wheels = [ [[package]] name = "tzlocal" -version = "5.3" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/33/cc/11360404b20a6340b9b4ed39a3338c4af47bc63f87f6cea94dbcbde07029/tzlocal-5.3.tar.gz", hash = "sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2", size = 30480 } +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/9f/1c0b69d3abf4c65acac051ad696b8aea55afbb746dea8017baab53febb5e/tzlocal-5.3-py3-none-any.whl", hash = "sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c", size = 17920 }, + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 }, ] [[package]] @@ -2114,7 +2125,7 @@ wheels = [ [[package]] name = "web3" -version = "7.8.0" +version = "7.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -2132,9 +2143,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/55/943b2b544afade2f1220e4587bd7e95f8a39465d96e0c6e5029a007c6bec/web3-7.8.0.tar.gz", hash = "sha256:712bc9fd6b1ef6e467ee24c25b581e1951cab2cba17f9f548f12587734f2c857", size = 2188875 } +sdist = { url = "https://files.pythonhosted.org/packages/5d/d7/f67b4947d89ab6a0c7a2079452e804368ebb4c36e47bfb12cf7641d93c60/web3-7.9.0.tar.gz", hash = "sha256:3c4487a7ac57e0a187bd7ee03db455d94f354d15cca45f097f15f7281ad1a01f", size = 2193782 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/52/bc4a08811db59392e13bf56ada316517a83b9a6135c20d357c222c80be2d/web3-7.8.0-py3-none-any.whl", hash = "sha256:c8771b3d8772f7104a0462804449beb57d36cef7bd8b411140f95a92fc46b559", size = 1363475 }, + { url = "https://files.pythonhosted.org/packages/ef/df/7ca4a880772a3eea99c994d40d142c96cb679e625dd4bce3cd79901e58d8/web3-7.9.0-py3-none-any.whl", hash = "sha256:7818267675283e9cae4487d2805fc34b899aa26f41b0000c798c79b1684899eb", size = 1365826 }, ] [[package]] From 829851fabd344af20f9ef478d40ecd28901207b2 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Tue, 18 Mar 2025 04:17:48 -0300 Subject: [PATCH 19/30] wip --- src/dipdup/cli.py | 45 ++++++++- src/dipdup/mcp.py | 243 ++++++++++++++++++++++++--------------------- src/dipdup/yaml.py | 24 +++-- 3 files changed, 185 insertions(+), 127 deletions(-) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 6a3b16530..506e82b5a 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -23,6 +23,7 @@ from dipdup import __version__ from dipdup import env from dipdup._version import check_version +from dipdup.config import McpConfig from dipdup.exceptions import CallbackError from dipdup.install import EPILOG from dipdup.install import WELCOME_ASCII @@ -543,28 +544,62 @@ async def mcp(ctx: click.Context) -> None: async def mcp_run(ctx: click.Context) -> None: """Run MCP server.""" + import uvicorn from anyio import from_thread + from mcp.server.sse import SseServerTransport + from starlette.applications import Starlette + from starlette.routing import Mount + from starlette.routing import Route + from dipdup import mcp from dipdup.config import DipDupConfig from dipdup.dipdup import DipDup - from dipdup.mcp import configure_mcp - from dipdup.mcp import get_mcp config: DipDupConfig = ctx.obj.config dipdup = DipDup(config) - mcp = get_mcp() - configure_mcp(dipdup._ctx) + mcp_config = config.mcp + if not mcp_config: + mcp_config = McpConfig() + + mcp.set_ctx(dipdup._ctx) # NOTE: Import all submodules to find @mcp.tool decorators dipdup._ctx.package.verify() + # NOTE: Run MCP in a separate thread to avoid blocking the DB connection with from_thread.start_blocking_portal() as portal: async with AsyncExitStack() as stack: await dipdup._create_datasources() await dipdup._set_up_database(stack) - portal.call(mcp.run_sse_async) + sse = SseServerTransport('/messages/') + + async def handle_sse(request: Any) -> None: + async with sse.connect_sse(request.scope, request.receive, request._send) as streams: + await mcp._app.run( + read_stream=streams[0], + write_stream=streams[1], + initialization_options=mcp._app.create_initialization_options(), + raise_exceptions=False, + ) + + starlette_app = Starlette( + debug=True, + routes=[ + Route('/sse', endpoint=handle_sse), + Mount('/messages/', app=sse.handle_post_message), + ], + ) + + config = uvicorn.Config( + starlette_app, + host=mcp_config.host, + port=mcp_config.port, + log_level='debug', + ) + server = uvicorn.Server(config) + portal.call(server.serve) @hasura.command(name='configure') diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index 37fc52914..6ab7d7d9d 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -1,26 +1,140 @@ -from collections.abc import Callable -from typing import TYPE_CHECKING +import logging from typing import Any +from pydantic import AnyUrl + from dipdup import models from dipdup.context import DipDupContext +from dipdup.utils import json_dumps -if TYPE_CHECKING: - from mcp.server.fastmcp import FastMCP +_logger = logging.getLogger(__name__) -_mcp: 'FastMCP | None' = None _ctx: DipDupContext | None = None +import mcp.server + +mcp.server.logger = _logger + +import mcp.types as types + + +def get_ctx() -> DipDupContext: + global _ctx + if _ctx is None: + raise ValueError('DipDup context is not initialized') + return _ctx + -async def _resource_config() -> str: +def set_ctx(ctx: DipDupContext): + global _ctx + _ctx = ctx + + +_app: mcp.server.Server = mcp.server.Server(name='DipDup') + + +@_app.list_tools() # type: ignore[no-untyped-call,misc] +async def list_tools() -> list[types.Tool]: + return [] + # return [ + # types.Tool( + # name='config', + # description='Dump the current indexer configuration in YAML format', + # inputSchema={ + # 'type': 'object', + # 'properties': {}, + # }, + # ), + # types.Tool( + # name='metrics', + # description='Show the current indexer metrics', + # inputSchema={ + # 'type': 'object', + # 'properties': {}, + # }, + # ), + # types.Tool( + # name='heads', + # description='Show the current datasource head blocks', + # inputSchema={ + # 'type': 'object', + # 'properties': {}, + # }, + # ), + # types.Tool( + # name='indexes', + # description='Show the current indexer state', + # inputSchema={ + # 'type': 'object', + # 'properties': {}, + # }, + # ), + # ] + + +@_app.list_resources() # type: ignore[no-untyped-call,misc] +async def list_resources() -> list[types.Resource]: + return [ + types.Resource( + uri=AnyUrl('dipdup://config'), + name='config', + description='Dump the current indexer configuration in YAML format', + mimeType='application/yaml', + ), + types.Resource( + uri=AnyUrl('dipdup://metrics'), + name='metrics', + description='Show the current indexer metrics', + mimeType='application/json', + ), + types.Resource( + uri=AnyUrl('dipdup://heads'), + name='heads', + description='Show the current datasource head blocks', + mimeType='application/json', + ), + types.Resource( + uri=AnyUrl('dipdup://indexes'), + name='indexes', + description='Show the current indexer state', + mimeType='application/json', + ), + ] + + +@_app.call_tool() # type: ignore[no-untyped-call,misc] +async def call_tool(name: str, arguments: dict) -> list[types.TextContent]: + # if name == 'config': + # return [types.TextContent(type='text', text=str(await _resource_config()))] + # if name == 'metrics': + # return [types.TextContent(type='text', text=str(await _resource_metrics()))] + # if name == 'heads': + # return [types.TextContent(type='text', text=str(await _resource_heads()))] + # if name == 'indexes': + # return [types.TextContent(type='text', text=str(await _resource_indexes()))] + return [] + + +@_app.read_resource() # type: ignore[no-untyped-call,misc] +async def read_resource(uri: AnyUrl) -> str: + uri = str(uri) + if uri == 'dipdup://config': + res = await _resource_config() + elif uri == 'dipdup://metrics': + res = await _resource_metrics() + elif uri == 'dipdup://heads': + res = await _resource_heads() + elif uri == 'dipdup://indexes': + res = await _resource_indexes() + else: + raise NotImplementedError(uri) + + return json_dumps(res) + + +async def _resource_config() -> dict[str, Any]: assert _ctx - secret_keys = {'password', 'api_key', 'secret'} - dump = _ctx.config.dump() - # TODO: More accurate filtering - return '\n'.join( - line if not any(key in line for key in secret_keys) else f'{line.split(":")[0]}: ***' - for line in '\n'.split(dump) - ) + return _ctx.config._json.dump(strip_secrets=True) async def _resource_metrics() -> dict[str, Any]: @@ -57,105 +171,4 @@ async def _resource_indexes() -> list[dict[str, Any]]: 'updated_at': m.updated_at, } ) - - -def _create_mcp() -> 'FastMCP': - from mcp.server.fastmcp import FastMCP - - mcp = FastMCP( - 'DipDup', - # FIXME: both not working - debug=True, - log_level='DEBUG', - ) - - # NOTE: Internal tools - - mcp.resource( - uri='dipdup://config', - name='Config', - description='Dump the current indexer configuration in YAML format', - mime_type='application/yaml', - )(_resource_config) - - mcp.resource( - uri='dipdup://metrics', - name='Metrics', - description='Show the current indexer metrics', - mime_type='application/json', - )(_resource_metrics) - - mcp.resource( - uri='dipdup://heads', - name='Heads', - description='Show the current datasource head blocks', - mime_type='application/json', - )(_resource_heads) - - mcp.resource( - uri='dipdup://indexes', - name='Indexes', - description='Show the current indexer state', - mime_type='application/json', - )(_resource_indexes) - - return mcp - - -def configure_mcp(ctx: DipDupContext) -> None: - global _ctx - _ctx = ctx - - if mcp_config := ctx.config.mcp: - mcp = get_mcp() - mcp.settings.host = mcp_config.host - mcp.settings.port = mcp_config.port - - -def get_mcp() -> 'FastMCP': - global _mcp - - if not _mcp: - _mcp = _create_mcp() - - return _mcp - - -def tool( - name: str, - description: str, -) -> Callable[..., None]: - assert ' ' not in name, 'Name should not contain spaces' - return get_mcp().tool( - name=name, - description=description, - ) - - -def resource( - self, - uri: str, - *, - name: str | None = None, - description: str | None = None, - mime_type: str | None = None, -) -> Callable[..., None]: - assert ' ' not in name, 'Name should not contain spaces' - return get_mcp().resource( - uri, - name=name, - description=description, - mime_type=mime_type, - ) - - -def prompt( - self, - name: str | None = None, - description: str | None = None, -) -> Callable[..., None]: - assert ' ' not in name, 'Name should not contain spaces' - return get_mcp().prompt( - name=name, - description=description, - ) + return res diff --git a/src/dipdup/yaml.py b/src/dipdup/yaml.py index 3f30fb710..d948ebba9 100644 --- a/src/dipdup/yaml.py +++ b/src/dipdup/yaml.py @@ -41,11 +41,21 @@ yaml_dumper.indent(mapping=2, sequence=4, offset=2) -def exclude_none(config_json: Any) -> Any: +def filter_config_json(config_json: Any, strip_secrets: bool = False) -> Any: + """Exclude `None` values, private fields and secrets from config JSON.""" + secrets = {'password', 'api_key', 'secret'} if isinstance(config_json, list | tuple): - return [exclude_none(i) for i in config_json if i is not None] + return [ + filter_config_json(i, strip_secrets) + for i in config_json + if i is not None and not (strip_secrets and isinstance(i, str) and any(s in i for s in secrets)) + ] if isinstance(config_json, dict): - return {k: exclude_none(v) for k, v in config_json.items() if v is not None and not k.startswith('_')} + return { + k: filter_config_json(v, strip_secrets) + for k, v in config_json.items() + if v is not None and not k.startswith('_') and not (strip_secrets and any(s in k for s in secrets)) + } return config_json @@ -77,8 +87,8 @@ def read_config_yaml(path: Path) -> str: raise ConfigurationError(f'Config file `{path}` is not readable: {e}') from e -def dump(value: dict[str, Any]) -> str: - value = exclude_none(value) +def dump(value: dict[str, Any], strip_secrets: bool = False) -> str: + value = filter_config_json(value, strip_secrets) buffer = StringIO() yaml_dumper.dump(value, buffer) return buffer.getvalue() @@ -162,5 +172,5 @@ def load( return config, config_environment - def dump(self) -> str: - return dump(self) + def dump(self, strip_secrets: bool = False) -> str: + return dump(self, strip_secrets) From 8f27844daebfb55233b46e6bc7ec2a7fe1f6b440 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Tue, 18 Mar 2025 07:25:47 -0300 Subject: [PATCH 20/30] done --- src/dipdup/cli.py | 6 +- src/dipdup/mcp.py | 281 +++++++++++++++++++++++++++------------------- 2 files changed, 167 insertions(+), 120 deletions(-) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 506e82b5a..9392e0fd0 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -592,13 +592,13 @@ async def handle_sse(request: Any) -> None: ], ) - config = uvicorn.Config( - starlette_app, + uv_config = uvicorn.Config( + app=starlette_app, host=mcp_config.host, port=mcp_config.port, log_level='debug', ) - server = uvicorn.Server(config) + server = uvicorn.Server(uv_config) portal.call(server.serve) diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index 6ab7d7d9d..ba7fad66d 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -1,5 +1,9 @@ +from __future__ import annotations + import logging +from typing import TYPE_CHECKING from typing import Any +from typing import cast from pydantic import AnyUrl @@ -12,10 +16,96 @@ _ctx: DipDupContext | None = None import mcp.server +import mcp.types as types -mcp.server.logger = _logger +if TYPE_CHECKING: + from collections.abc import Awaitable + from collections.abc import Callable + from collections.abc import Iterable -import mcp.types as types +# NOTE: Resource and tool callbacks + + +async def _resource_config() -> str: + return get_ctx().config._json.dump(strip_secrets=True) + + +async def _resource_metrics() -> dict[str, Any]: + metrics_model = await models.Meta.get_or_none(key='dipdup_metrics') + if metrics_model: + return cast('dict[str, Any]', metrics_model.value) + return {} + + +async def _resource_heads() -> list[dict[str, Any]]: + res = [] + for m in await models.Head.all(): + res.append( + { + 'datasource_name': m.name, + 'level': m.level, + 'hash': m.hash, + 'timestamp': m.timestamp.strftime('%Y-%m-%d %H:%M:%S'), + 'updated_at': m.updated_at.strftime('%Y-%m-%d %H:%M:%S'), + } + ) + return res + + +async def _resource_indexes() -> list[dict[str, Any]]: + res = [] + for m in await models.Index.all(): + res.append( + { + 'name': m.name, + 'kind': m.type.value, + 'status': m.status.value, + 'height': m.level, + 'updated_at': m.updated_at.strftime('%Y-%m-%d %H:%M:%S'), + } + ) + return res + + +# NOTE: Built-in tools and resources + +DIPDUP_RESOURCES: dict[str, types.Resource] = { + 'config': types.Resource( + uri=AnyUrl('dipdup://config'), + name='config', + description='Dump the current indexer configuration in YAML format', + mimeType='text/plain', + ), + 'metrics': types.Resource( + uri=AnyUrl('dipdup://metrics'), + name='metrics', + description='Show the current indexer metrics', + mimeType='application/json', + ), + 'heads': types.Resource( + uri=AnyUrl('dipdup://heads'), + name='heads', + description='Show the current datasource head blocks', + mimeType='application/json', + ), + 'indexes': types.Resource( + uri=AnyUrl('dipdup://indexes'), + name='indexes', + description='Show the current indexer state', + mimeType='application/json', + ), +} +DIPDUP_RESOURCES_FN: dict[str, Callable[..., Awaitable[Any]]] = { + 'config': _resource_config, + 'metrics': _resource_metrics, + 'heads': _resource_heads, + 'indexes': _resource_indexes, +} + +DIPDUP_TOOLS: dict[str, types.Tool] = {} +DIPDUP_TOOLS_FN: dict[str, Callable[..., Awaitable[Iterable[str]]]] = {} + +# NOTE: Context management def get_ctx() -> DipDupContext: @@ -25,150 +115,107 @@ def get_ctx() -> DipDupContext: return _ctx -def set_ctx(ctx: DipDupContext): +def set_ctx(ctx: DipDupContext) -> None: global _ctx _ctx = ctx -_app: mcp.server.Server = mcp.server.Server(name='DipDup') +_app: mcp.server.Server[Any] = mcp.server.Server(name='DipDup') +_user_tools: dict[str, types.Tool] = {} +_user_tools_fn: dict[str, Callable[..., Iterable[str]]] = {} +_user_resources: dict[str, types.Resource] = {} +_user_resources_fn: dict[str, Callable[..., Iterable[str]]] = {} +# TODO: Push typehints to upstream @_app.list_tools() # type: ignore[no-untyped-call,misc] async def list_tools() -> list[types.Tool]: - return [] - # return [ - # types.Tool( - # name='config', - # description='Dump the current indexer configuration in YAML format', - # inputSchema={ - # 'type': 'object', - # 'properties': {}, - # }, - # ), - # types.Tool( - # name='metrics', - # description='Show the current indexer metrics', - # inputSchema={ - # 'type': 'object', - # 'properties': {}, - # }, - # ), - # types.Tool( - # name='heads', - # description='Show the current datasource head blocks', - # inputSchema={ - # 'type': 'object', - # 'properties': {}, - # }, - # ), - # types.Tool( - # name='indexes', - # description='Show the current indexer state', - # inputSchema={ - # 'type': 'object', - # 'properties': {}, - # }, - # ), - # ] + return [ + *list(DIPDUP_TOOLS.values()), + *list(_user_tools.values()), + ] @_app.list_resources() # type: ignore[no-untyped-call,misc] async def list_resources() -> list[types.Resource]: return [ - types.Resource( - uri=AnyUrl('dipdup://config'), - name='config', - description='Dump the current indexer configuration in YAML format', - mimeType='application/yaml', - ), - types.Resource( - uri=AnyUrl('dipdup://metrics'), - name='metrics', - description='Show the current indexer metrics', - mimeType='application/json', - ), - types.Resource( - uri=AnyUrl('dipdup://heads'), - name='heads', - description='Show the current datasource head blocks', - mimeType='application/json', - ), - types.Resource( - uri=AnyUrl('dipdup://indexes'), - name='indexes', - description='Show the current indexer state', - mimeType='application/json', - ), + *list(DIPDUP_RESOURCES.values()), + *list(_user_resources.values()), ] +# FIXME: Not supported +@_app.list_resource_templates() # type: ignore[no-untyped-call,misc] +async def list_resource_templates() -> list[types.ResourceTemplate]: + return [] + @_app.call_tool() # type: ignore[no-untyped-call,misc] -async def call_tool(name: str, arguments: dict) -> list[types.TextContent]: - # if name == 'config': - # return [types.TextContent(type='text', text=str(await _resource_config()))] - # if name == 'metrics': - # return [types.TextContent(type='text', text=str(await _resource_metrics()))] - # if name == 'heads': - # return [types.TextContent(type='text', text=str(await _resource_heads()))] - # if name == 'indexes': - # return [types.TextContent(type='text', text=str(await _resource_indexes()))] - return [] +async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.TextContent]: + if name in _user_tools_fn: + res = await _user_tools_fn[name](**arguments) + return [types.TextContent(type='text', text=res)] + + if name in DIPDUP_TOOLS_FN: + res = await DIPDUP_TOOLS_FN[name](**arguments) + return [types.TextContent(type='text', text=res)] + + raise NotImplementedError(name) @_app.read_resource() # type: ignore[no-untyped-call,misc] async def read_resource(uri: AnyUrl) -> str: - uri = str(uri) - if uri == 'dipdup://config': - res = await _resource_config() - elif uri == 'dipdup://metrics': - res = await _resource_metrics() - elif uri == 'dipdup://heads': - res = await _resource_heads() - elif uri == 'dipdup://indexes': - res = await _resource_indexes() - else: - raise NotImplementedError(uri) - return json_dumps(res) + if uri.scheme != 'dipdup': + raise ValueError(f'Invalid scheme: {uri.scheme}') + name = uri.host.lstrip('/') + if name in _user_resources_fn: + res = await _user_resources_fn[name]() + elif name in DIPDUP_RESOURCES_FN: + res = await DIPDUP_RESOURCES_FN[name]() + else: + raise NotImplementedError(name) -async def _resource_config() -> dict[str, Any]: - assert _ctx - return _ctx.config._json.dump(strip_secrets=True) + # FIXME: mimeType is always `text/plain` + return json_dumps(res, None).decode() -async def _resource_metrics() -> dict[str, Any]: - metrics_model = await models.Meta.get_or_none(key='dipdup_metrics') - if metrics_model: - return metrics_model.value - return {} +def tool(name: str, description: str) -> Any: + def wrapper(func: Any) -> Any: + global _user_tools + global _user_tools_fn + if name in _user_tools or name in DIPDUP_TOOLS: + raise ValueError(f'Tool `{name}` is already registered') -async def _resource_heads() -> list[dict[str, Any]]: - res = [] - for m in await models.Head.all(): - res.append( - { - 'datasource_name': m.name, - 'level': m.level, - 'hash': m.hash, - 'timestamp': m.timestamp, - 'updated_at': m.updated_at, - } + _user_tools[name] = types.Tool( + name=name, + description=description, + # FIXME: Generate schema from signature + inputSchema={'type': 'object'}, ) - return res + _user_tools_fn[name] = func + return func -async def _resource_indexes() -> list[dict[str, Any]]: - res = [] - for m in await models.Index.all(): - res.append( - { - 'name': m.name, - 'kind': m.type, - 'status': m.status, - 'height': m.level, - 'updated_at': m.updated_at, - } + return wrapper + + +def resource(name: str, description: str, mime_type: str) -> Any: + def wrapper(func: Any) -> Any: + global _user_resources + global _user_resources_fn + + if name in _user_resources or name in DIPDUP_RESOURCES: + raise ValueError(f'Resource `{name}` is already registered') + + _user_resources[name] = types.Resource( + uri=AnyUrl(f'dipdup://{name}'), + name=name, + description=description, + mimeType=mime_type, ) - return res + _user_resources_fn[name] = func + return func + + return wrapper From e82383b745f7df370a4d72e333bbf3336ace7021 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Wed, 19 Mar 2025 19:33:54 -0300 Subject: [PATCH 21/30] docs, lint --- docs/5.advanced/7.mcp.md | 107 ++++++++++++++++++++++++++++++++ docs/7.references/2.config.md | 2 +- docs/7.references/4.models.md | 2 +- docs/9.release-notes/5.v7.5.md | 2 +- schemas/dipdup-3.0.json | 33 ++++++++++ src/demo_evm_events/__init__.py | 12 ---- src/dipdup/config/__init__.py | 3 +- src/dipdup/mcp.py | 7 ++- 8 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 docs/5.advanced/7.mcp.md diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md new file mode 100644 index 000000000..551f66554 --- /dev/null +++ b/docs/5.advanced/7.mcp.md @@ -0,0 +1,107 @@ +--- +title: "MCP" +description: "MCP is an open protocol that standardizes how applications provide context to LLMs. This document explains how to integrate DipDup with MCP clients and implement custom tools and resources." +--- + +# MCP integration + +## Introduction + +The **Model Context Protocol** (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Whether you're building an AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to connect LLMs with the context they need. + +**DipDup** provides an MCP server with several built-in resources to help LLMs understand the context of your project and the current state of the indexer. + +You can also implement your own tools and resources specific to your project. + +## Running MCP server + +DipDup MCP server runs in a separate process. To start it, run the following command: + +```shell +dipdup mcp run +``` + +Make sure that you use the same database connection settings as for the indexer. + +By default, the server listens on `http://127.0.0.1:9999`. You can change the settings in the `mcp` section of the configuration file. + +```yaml [dipdup.yaml] +mcp: + host: 127.0.0.1 + port: 9999 +``` + +## Connecting clients + +### Cursor + +To configure MCP servers go to `File -> Preferences -> Cursor Settings -> MCP`. + +Add the following configuration to `~/.cursor/mcp.json`: + +```json [~/.cursor/mcp.json] +{ + "mcpServers": { + "local": { + "url": "http://127.0.0.1:9999/sse" + } + } +} +``` + +If your server is running, but Cursor doesn't connect, try "Reload Window" in the Command Palette. + +### VSCode (Copilot) + +There is no official support for MCP in VSCode yet. + +There are two extensions available in repository to add MCP capabilities to Copilot. None of them worked out-of-the-box at the time of writing this document. If have better luck, please let us know in GitHub issues. + +- [Copilot MCP](https://marketplace.visualstudio.com/items?itemName=AutomataLabs.copilot-mcp) +- [MCP-Client](https://marketplace.visualstudio.com/items?itemName=m1self.mcp-client) + +### Claude Desktop + +[Claude Desktop](https://claude.ai/download) currently only supports stdio-based MCP servers. You can try [lightconetech/mcp-gateway](https://github.com/lightconetech/mcp-gateway), a stdio-to-http gateway created to fill this gap. + +## Implementing MCP primitives + +To make your server more useful, you can implement custom MCP primitives specific to your project. For example, let's implement a tool that provides some information about token holders. + +Create a new file `mcp.py` in the `handlers` directory: + +```python [handlers/mcp.py] +from dipdup import mcp + +from demo_evm_events import models + +@mcp.tool('TopHolders', 'Get top token holders') +async def tool_holders() -> str: + holders = await models.Holder.filter().order_by('-balance').limit(10).all() + res = 'Top holders:\n' + for holder in holders: + res += f'{holder.address}: {holder.balance}\n' + return res +``` + +In this example, we define a new tool `TopHolders` that returns the top 10 token holders sorted by their balance. + +### Debugging + +To check if your tool is working correctly before using it in the client, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) app. + +Run `npx @modelcontextprotocol/inspector`, open `http://127.0.0.1:5173/` in your browser, choose SSE transport and connect to `http://127.0.0.1:9999/sse`. + +If you suspect there's a bug in the framework, you can enable full logging to get some insights into the MCP server: + +```yaml [dipdup.yaml] +logging: + '': DEBUG +``` + +## Further reading + +- [For Server Developers - Model Context Protocol](https://modelcontextprotocol.io/quickstart/server) +- [Example Servers - Model Context Protocol](https://modelcontextprotocol.io/examples) +- [qpd-v/mcp-guide](https://github.com/qpd-v/mcp-guide) - a tutorial server that helps users understand MCP concepts, provides interactive examples, and demonstrates best practices for building MCP integrations. Also, an awesome-list of related projects. +- [anjor/coinmarket-mcp-server](https://github.com/anjor/coinmarket-mcp-server) - example MCP server for CoinMarketCap API. diff --git a/docs/7.references/2.config.md b/docs/7.references/2.config.md index e755ae1b9..80e679399 100644 --- a/docs/7.references/2.config.md +++ b/docs/7.references/2.config.md @@ -32,8 +32,8 @@ description: "Config file reference"
                      • advanced (AdvancedConfig) – Advanced config

                      • custom (dict[str, Any]) – User-defined configuration to use in callbacks

                      • logging (dict[str, str | int] | str | int) – Modify logging verbosity

                      • +
                      • mcp (McpConfig | None) – MCP server config

                      • -
                      • mcp (McpConfig | None)

                      diff --git a/docs/7.references/4.models.md b/docs/7.references/4.models.md index ea70d49e1..739bbcfa0 100644 --- a/docs/7.references/4.models.md +++ b/docs/7.references/4.models.md @@ -120,7 +120,7 @@ description: "Models reference" ## dipdup.models.IndexType class dipdup.models.IndexType(*values) -

                      Enum for dipdup.models.Index

                      +

                      Kind of the index

                      diff --git a/docs/9.release-notes/5.v7.5.md b/docs/9.release-notes/5.v7.5.md index 77666a548..3d1fd408f 100644 --- a/docs/9.release-notes/5.v7.5.md +++ b/docs/9.release-notes/5.v7.5.md @@ -15,7 +15,7 @@ A bunch of performance improvements have been made in this release. DipDup now i The Hasura adapter now supports the `bulk` request type to apply table customizations faster and organize custom metadata files more conveniently. -Finally, DipDup 6.5, the stable release branch, has reached end-of-life. 6.5.16 is the last release in this branch. Please, follow the [7.0 Migration Guide](../9.release-notes/9.v7.0.md#migration-guide) to upgrade to the latest version. +Finally, DipDup 6.5, the stable release branch, has reached end-of-life. 6.5.16 is the last release in this branch. Please, follow the [7.0 Migration Guide](../9.release-notes/10.v7.0.md#migration-guide) to upgrade to the latest version. {{ #include 9.release-notes/_7.5_changelog.md }} {{ #include 9.release-notes/_footer.md }} diff --git a/schemas/dipdup-3.0.json b/schemas/dipdup-3.0.json index b2df1ad24..459c28dac 100644 --- a/schemas/dipdup-3.0.json +++ b/schemas/dipdup-3.0.json @@ -1105,6 +1105,26 @@ "title": "JobConfig", "type": "object" }, + "McpConfig": { + "additionalProperties": false, + "description": "Config for MCP server", + "properties": { + "host": { + "default": "127.0.0.1", + "description": "Host to bind to", + "title": "host", + "type": "string" + }, + "port": { + "default": 9999, + "description": "Port to bind to", + "title": "port", + "type": "integer" + } + }, + "title": "McpConfig", + "type": "object" + }, "PostgresDatabaseConfig": { "additionalProperties": false, "description": "Postgres database connection config", @@ -3196,6 +3216,19 @@ "description": "Modify logging verbosity", "title": "logging" }, + "mcp": { + "anyOf": [ + { + "$ref": "#/$defs/McpConfig" + }, + { + "type": "null" + } + ], + "default": null, + "description": "MCP server config", + "title": "mcp" + }, "package": { "description": "Name of indexer's Python package, existing or not", "title": "package", diff --git a/src/demo_evm_events/__init__.py b/src/demo_evm_events/__init__.py index 905c01a96..e69de29bb 100644 --- a/src/demo_evm_events/__init__.py +++ b/src/demo_evm_events/__init__.py @@ -1,12 +0,0 @@ -from dipdup import mcp - -from demo_evm_events import models - - -@mcp.tool('TopHolders', 'Top holders stats') -async def tool_holders() -> str: - holders = await models.Holder.filter().order_by('-balance').limit(10).all() - res = 'Top holders:\n' - for holder in holders: - res += f'{holder.address}: {holder.balance}\n' - return res diff --git a/src/dipdup/config/__init__.py b/src/dipdup/config/__init__.py index fb7fd242d..b6b0ef77f 100644 --- a/src/dipdup/config/__init__.py +++ b/src/dipdup/config/__init__.py @@ -547,7 +547,7 @@ class ApiConfig: @dataclass(config=ConfigDict(extra='forbid', defer_build=True), kw_only=True) class McpConfig: - """Config for MCP integration. + """Config for MCP server :param host: Host to bind to :param port: Port to bind to @@ -603,6 +603,7 @@ class DipDupConfig(InteractiveMixin): :param advanced: Advanced config :param custom: User-defined configuration to use in callbacks :param logging: Modify logging verbosity + :param mcp: MCP server config """ spec_version: ToStr diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index ba7fad66d..386d7fb17 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -122,9 +122,9 @@ def set_ctx(ctx: DipDupContext) -> None: _app: mcp.server.Server[Any] = mcp.server.Server(name='DipDup') _user_tools: dict[str, types.Tool] = {} -_user_tools_fn: dict[str, Callable[..., Iterable[str]]] = {} +_user_tools_fn: dict[str, Callable[..., Awaitable[Iterable[str]]]] = {} _user_resources: dict[str, types.Resource] = {} -_user_resources_fn: dict[str, Callable[..., Iterable[str]]] = {} +_user_resources_fn: dict[str, Callable[..., Awaitable[Iterable[str]]]] = {} # TODO: Push typehints to upstream @@ -143,6 +143,7 @@ async def list_resources() -> list[types.Resource]: *list(_user_resources.values()), ] + # FIXME: Not supported @_app.list_resource_templates() # type: ignore[no-untyped-call,misc] async def list_resource_templates() -> list[types.ResourceTemplate]: @@ -168,7 +169,7 @@ async def read_resource(uri: AnyUrl) -> str: if uri.scheme != 'dipdup': raise ValueError(f'Invalid scheme: {uri.scheme}') - name = uri.host.lstrip('/') + name = uri.host.lstrip('/') # type: ignore[union-attr] if name in _user_resources_fn: res = await _user_resources_fn[name]() elif name in DIPDUP_RESOURCES_FN: From 87943a142a4f7cbd659b8277e191749b63ca8220 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Mar 2025 13:33:52 -0300 Subject: [PATCH 22/30] claude notes --- docs/5.advanced/7.mcp.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index 551f66554..e2e592ec8 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -62,7 +62,13 @@ There are two extensions available in repository to add MCP capabilities to Copi ### Claude Desktop -[Claude Desktop](https://claude.ai/download) currently only supports stdio-based MCP servers. You can try [lightconetech/mcp-gateway](https://github.com/lightconetech/mcp-gateway), a stdio-to-http gateway created to fill this gap. +[Claude Desktop](https://claude.ai/download) currently only supports stdio-based MCP servers. You can use [supercorp-ai/supergateway](https://github.com/supercorp-ai/supergateway) tool to connect DipDup MCP server to Claude Desktop: + +```shell [Terminal] +npx -y supergateway --sse http://127.0.0.1:9999 +``` + +There is also [lightconetech/mcp-gateway](https://github.com/lightconetech/mcp-gateway) tool available for the same purpose. ## Implementing MCP primitives From 5a3e3d6bb41e6e8e8683a49414be67a5a4ee0c18 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Mar 2025 13:43:56 -0300 Subject: [PATCH 23/30] update template --- src/demo_blank/Makefile | 7 +- src/demo_blank/configs/dipdup.compose.yaml | 8 ++- src/demo_blank/configs/dipdup.sqlite.yaml | 8 ++- src/demo_blank/configs/dipdup.swarm.yaml | 8 ++- src/demo_blank/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_evm_events/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- src/demo_evm_events/configs/dipdup.swarm.yaml | 8 ++- src/demo_evm_events/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_evm_events/dipdup.yaml | 4 +- src/demo_evm_transactions/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_evm_uniswap/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- src/demo_evm_uniswap/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_starknet_events/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_substrate_events/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_auction/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_auction/dipdup.yaml | 17 ++--- src/demo_tezos_dao/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- src/demo_tezos_dao/configs/dipdup.sqlite.yaml | 8 ++- src/demo_tezos_dao/configs/dipdup.swarm.yaml | 8 ++- src/demo_tezos_dao/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_dex/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- src/demo_tezos_dex/configs/dipdup.sqlite.yaml | 8 ++- src/demo_tezos_dex/configs/dipdup.swarm.yaml | 8 ++- src/demo_tezos_dex/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_domains/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_events/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- src/demo_tezos_events/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_factories/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_head/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- src/demo_tezos_head/configs/dipdup.swarm.yaml | 8 ++- src/demo_tezos_head/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_nft_marketplace/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_raw/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- src/demo_tezos_raw/configs/dipdup.sqlite.yaml | 8 ++- src/demo_tezos_raw/configs/dipdup.swarm.yaml | 8 ++- src/demo_tezos_raw/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_token/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- src/demo_tezos_token/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_token_balances/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/demo_tezos_token_transfers/Makefile | 7 +- .../configs/dipdup.compose.yaml | 8 ++- .../configs/dipdup.sqlite.yaml | 8 ++- .../configs/dipdup.swarm.yaml | 8 ++- .../deploy/compose.mcp.yaml | 67 +++++++++++++++++++ src/dipdup/projects/base/Makefile.j2 | 9 ++- .../projects/base/deploy/compose.mcp.yaml | 67 +++++++++++++++++++ 94 files changed, 1735 insertions(+), 126 deletions(-) create mode 100644 src/demo_blank/deploy/compose.mcp.yaml create mode 100644 src/demo_evm_events/deploy/compose.mcp.yaml create mode 100644 src/demo_evm_transactions/deploy/compose.mcp.yaml create mode 100644 src/demo_evm_uniswap/deploy/compose.mcp.yaml create mode 100644 src/demo_starknet_events/deploy/compose.mcp.yaml create mode 100644 src/demo_substrate_events/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_auction/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_dao/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_dex/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_domains/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_events/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_factories/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_head/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_nft_marketplace/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_raw/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_token/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_token_balances/deploy/compose.mcp.yaml create mode 100644 src/demo_tezos_token_transfers/deploy/compose.mcp.yaml create mode 100644 src/dipdup/projects/base/deploy/compose.mcp.yaml diff --git a/src/demo_blank/Makefile b/src/demo_blank/Makefile index 58d80aa22..ab64725ce 100644 --- a/src/demo_blank/Makefile +++ b/src/demo_blank/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_blank/configs/dipdup.compose.yaml b/src/demo_blank/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_blank/configs/dipdup.compose.yaml +++ b/src/demo_blank/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_blank/configs/dipdup.sqlite.yaml b/src/demo_blank/configs/dipdup.sqlite.yaml index 1d6c41b76..a3b743fe4 100644 --- a/src/demo_blank/configs/dipdup.sqlite.yaml +++ b/src/demo_blank/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_blank/configs/dipdup.swarm.yaml b/src/demo_blank/configs/dipdup.swarm.yaml index 36eb14772..2caceff6a 100644 --- a/src/demo_blank/configs/dipdup.swarm.yaml +++ b/src/demo_blank/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_blank/deploy/compose.mcp.yaml b/src/demo_blank/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_blank/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_evm_events/Makefile b/src/demo_evm_events/Makefile index a48d49fc2..488754967 100644 --- a/src/demo_evm_events/Makefile +++ b/src/demo_evm_events/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_evm_events/configs/dipdup.compose.yaml b/src/demo_evm_events/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_evm_events/configs/dipdup.compose.yaml +++ b/src/demo_evm_events/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_events/configs/dipdup.sqlite.yaml b/src/demo_evm_events/configs/dipdup.sqlite.yaml index 83ddb8838..936193068 100644 --- a/src/demo_evm_events/configs/dipdup.sqlite.yaml +++ b/src/demo_evm_events/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_events/configs/dipdup.swarm.yaml b/src/demo_evm_events/configs/dipdup.swarm.yaml index 109637933..f18d2ca5d 100644 --- a/src/demo_evm_events/configs/dipdup.swarm.yaml +++ b/src/demo_evm_events/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_events/deploy/compose.mcp.yaml b/src/demo_evm_events/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_evm_events/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_evm_events/dipdup.yaml b/src/demo_evm_events/dipdup.yaml index cedf4f9c9..d14101c55 100644 --- a/src/demo_evm_events/dipdup.yaml +++ b/src/demo_evm_events/dipdup.yaml @@ -30,6 +30,4 @@ indexes: handlers: - callback: on_transfer contract: eth_usdt - name: Transfer - -mcp: {} \ No newline at end of file + name: Transfer \ No newline at end of file diff --git a/src/demo_evm_transactions/Makefile b/src/demo_evm_transactions/Makefile index 2092d2906..d7f6d4e5b 100644 --- a/src/demo_evm_transactions/Makefile +++ b/src/demo_evm_transactions/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_evm_transactions/configs/dipdup.compose.yaml b/src/demo_evm_transactions/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_evm_transactions/configs/dipdup.compose.yaml +++ b/src/demo_evm_transactions/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_transactions/configs/dipdup.sqlite.yaml b/src/demo_evm_transactions/configs/dipdup.sqlite.yaml index a3da667a6..013a6b225 100644 --- a/src/demo_evm_transactions/configs/dipdup.sqlite.yaml +++ b/src/demo_evm_transactions/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_transactions/configs/dipdup.swarm.yaml b/src/demo_evm_transactions/configs/dipdup.swarm.yaml index 921a981b5..be66a939b 100644 --- a/src/demo_evm_transactions/configs/dipdup.swarm.yaml +++ b/src/demo_evm_transactions/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_transactions/deploy/compose.mcp.yaml b/src/demo_evm_transactions/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_evm_transactions/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_evm_uniswap/Makefile b/src/demo_evm_uniswap/Makefile index 5b92732b5..b6cd87e22 100644 --- a/src/demo_evm_uniswap/Makefile +++ b/src/demo_evm_uniswap/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_evm_uniswap/configs/dipdup.compose.yaml b/src/demo_evm_uniswap/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_evm_uniswap/configs/dipdup.compose.yaml +++ b/src/demo_evm_uniswap/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_uniswap/configs/dipdup.sqlite.yaml b/src/demo_evm_uniswap/configs/dipdup.sqlite.yaml index 8d04cd1a7..b2380486a 100644 --- a/src/demo_evm_uniswap/configs/dipdup.sqlite.yaml +++ b/src/demo_evm_uniswap/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_uniswap/configs/dipdup.swarm.yaml b/src/demo_evm_uniswap/configs/dipdup.swarm.yaml index c0f5296d5..50f540b09 100644 --- a/src/demo_evm_uniswap/configs/dipdup.swarm.yaml +++ b/src/demo_evm_uniswap/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_evm_uniswap/deploy/compose.mcp.yaml b/src/demo_evm_uniswap/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_evm_uniswap/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_starknet_events/Makefile b/src/demo_starknet_events/Makefile index fa47b1032..2028f141c 100644 --- a/src/demo_starknet_events/Makefile +++ b/src/demo_starknet_events/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_starknet_events/configs/dipdup.compose.yaml b/src/demo_starknet_events/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_starknet_events/configs/dipdup.compose.yaml +++ b/src/demo_starknet_events/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_starknet_events/configs/dipdup.sqlite.yaml b/src/demo_starknet_events/configs/dipdup.sqlite.yaml index 1730e2290..e7e8fc909 100644 --- a/src/demo_starknet_events/configs/dipdup.sqlite.yaml +++ b/src/demo_starknet_events/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_starknet_events/configs/dipdup.swarm.yaml b/src/demo_starknet_events/configs/dipdup.swarm.yaml index 5c67dc423..af712e667 100644 --- a/src/demo_starknet_events/configs/dipdup.swarm.yaml +++ b/src/demo_starknet_events/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_starknet_events/deploy/compose.mcp.yaml b/src/demo_starknet_events/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_starknet_events/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_substrate_events/Makefile b/src/demo_substrate_events/Makefile index 1c6eb8fa4..f8bdcb3ef 100644 --- a/src/demo_substrate_events/Makefile +++ b/src/demo_substrate_events/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_substrate_events/configs/dipdup.compose.yaml b/src/demo_substrate_events/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_substrate_events/configs/dipdup.compose.yaml +++ b/src/demo_substrate_events/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_substrate_events/configs/dipdup.sqlite.yaml b/src/demo_substrate_events/configs/dipdup.sqlite.yaml index 119aab5b2..e02ac229c 100644 --- a/src/demo_substrate_events/configs/dipdup.sqlite.yaml +++ b/src/demo_substrate_events/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_substrate_events/configs/dipdup.swarm.yaml b/src/demo_substrate_events/configs/dipdup.swarm.yaml index 91290d8c8..a12e9c085 100644 --- a/src/demo_substrate_events/configs/dipdup.swarm.yaml +++ b/src/demo_substrate_events/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_substrate_events/deploy/compose.mcp.yaml b/src/demo_substrate_events/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_substrate_events/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_auction/Makefile b/src/demo_tezos_auction/Makefile index 598d8dcb2..670c1ba21 100644 --- a/src/demo_tezos_auction/Makefile +++ b/src/demo_tezos_auction/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_auction/configs/dipdup.compose.yaml b/src/demo_tezos_auction/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_auction/configs/dipdup.compose.yaml +++ b/src/demo_tezos_auction/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_auction/configs/dipdup.sqlite.yaml b/src/demo_tezos_auction/configs/dipdup.sqlite.yaml index f302f6caf..4c24b9c7e 100644 --- a/src/demo_tezos_auction/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_auction/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_auction/configs/dipdup.swarm.yaml b/src/demo_tezos_auction/configs/dipdup.swarm.yaml index cdbba5bc2..9df161c26 100644 --- a/src/demo_tezos_auction/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_auction/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_auction/deploy/compose.mcp.yaml b/src/demo_tezos_auction/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_auction/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_auction/dipdup.yaml b/src/demo_tezos_auction/dipdup.yaml index 12f897282..689bd930b 100644 --- a/src/demo_tezos_auction/dipdup.yaml +++ b/src/demo_tezos_auction/dipdup.yaml @@ -1,6 +1,3 @@ -mcp: {} -api: {} - spec_version: 3.0 package: demo_tezos_auction @@ -43,10 +40,10 @@ templates: destination: entrypoint: withdraw -# indexes: -# tzcolors: -# template: auction -# values: -# datasource: tzkt -# minter: tzcolors_minter -# auction: tzcolors_auction \ No newline at end of file +indexes: + tzcolors: + template: auction + values: + datasource: tzkt + minter: tzcolors_minter + auction: tzcolors_auction \ No newline at end of file diff --git a/src/demo_tezos_dao/Makefile b/src/demo_tezos_dao/Makefile index bb820215e..0c1d03421 100644 --- a/src/demo_tezos_dao/Makefile +++ b/src/demo_tezos_dao/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_dao/configs/dipdup.compose.yaml b/src/demo_tezos_dao/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_dao/configs/dipdup.compose.yaml +++ b/src/demo_tezos_dao/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_dao/configs/dipdup.sqlite.yaml b/src/demo_tezos_dao/configs/dipdup.sqlite.yaml index eb8a87dae..4a0f5f22b 100644 --- a/src/demo_tezos_dao/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_dao/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_dao/configs/dipdup.swarm.yaml b/src/demo_tezos_dao/configs/dipdup.swarm.yaml index 973ef4168..aa9305190 100644 --- a/src/demo_tezos_dao/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_dao/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_dao/deploy/compose.mcp.yaml b/src/demo_tezos_dao/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_dao/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_dex/Makefile b/src/demo_tezos_dex/Makefile index 7aaa2b156..1ea571baa 100644 --- a/src/demo_tezos_dex/Makefile +++ b/src/demo_tezos_dex/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_dex/configs/dipdup.compose.yaml b/src/demo_tezos_dex/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_dex/configs/dipdup.compose.yaml +++ b/src/demo_tezos_dex/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_dex/configs/dipdup.sqlite.yaml b/src/demo_tezos_dex/configs/dipdup.sqlite.yaml index 630e591e3..dd7b94539 100644 --- a/src/demo_tezos_dex/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_dex/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_dex/configs/dipdup.swarm.yaml b/src/demo_tezos_dex/configs/dipdup.swarm.yaml index 113dd97eb..759e0c8dc 100644 --- a/src/demo_tezos_dex/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_dex/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_dex/deploy/compose.mcp.yaml b/src/demo_tezos_dex/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_dex/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_domains/Makefile b/src/demo_tezos_domains/Makefile index 3cdb71a7e..f29409011 100644 --- a/src/demo_tezos_domains/Makefile +++ b/src/demo_tezos_domains/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_domains/configs/dipdup.compose.yaml b/src/demo_tezos_domains/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_domains/configs/dipdup.compose.yaml +++ b/src/demo_tezos_domains/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_domains/configs/dipdup.sqlite.yaml b/src/demo_tezos_domains/configs/dipdup.sqlite.yaml index 2909e2de4..7fc06c608 100644 --- a/src/demo_tezos_domains/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_domains/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_domains/configs/dipdup.swarm.yaml b/src/demo_tezos_domains/configs/dipdup.swarm.yaml index cb56d992f..e02b4f916 100644 --- a/src/demo_tezos_domains/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_domains/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_domains/deploy/compose.mcp.yaml b/src/demo_tezos_domains/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_domains/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_events/Makefile b/src/demo_tezos_events/Makefile index aa32ac445..de736f5c8 100644 --- a/src/demo_tezos_events/Makefile +++ b/src/demo_tezos_events/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_events/configs/dipdup.compose.yaml b/src/demo_tezos_events/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_events/configs/dipdup.compose.yaml +++ b/src/demo_tezos_events/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_events/configs/dipdup.sqlite.yaml b/src/demo_tezos_events/configs/dipdup.sqlite.yaml index c913ee9db..f83133c5c 100644 --- a/src/demo_tezos_events/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_events/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_events/configs/dipdup.swarm.yaml b/src/demo_tezos_events/configs/dipdup.swarm.yaml index 3dd5dfcee..1b6485e2b 100644 --- a/src/demo_tezos_events/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_events/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_events/deploy/compose.mcp.yaml b/src/demo_tezos_events/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_events/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_factories/Makefile b/src/demo_tezos_factories/Makefile index f53b618b8..3f78be458 100644 --- a/src/demo_tezos_factories/Makefile +++ b/src/demo_tezos_factories/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_factories/configs/dipdup.compose.yaml b/src/demo_tezos_factories/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_factories/configs/dipdup.compose.yaml +++ b/src/demo_tezos_factories/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_factories/configs/dipdup.sqlite.yaml b/src/demo_tezos_factories/configs/dipdup.sqlite.yaml index 6f12866e8..1f28f1537 100644 --- a/src/demo_tezos_factories/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_factories/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_factories/configs/dipdup.swarm.yaml b/src/demo_tezos_factories/configs/dipdup.swarm.yaml index f1ab8d2a7..56368db65 100644 --- a/src/demo_tezos_factories/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_factories/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_factories/deploy/compose.mcp.yaml b/src/demo_tezos_factories/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_factories/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_head/Makefile b/src/demo_tezos_head/Makefile index a51cdec8f..f3f1c155b 100644 --- a/src/demo_tezos_head/Makefile +++ b/src/demo_tezos_head/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_head/configs/dipdup.compose.yaml b/src/demo_tezos_head/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_head/configs/dipdup.compose.yaml +++ b/src/demo_tezos_head/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_head/configs/dipdup.sqlite.yaml b/src/demo_tezos_head/configs/dipdup.sqlite.yaml index 1982997bf..1334add06 100644 --- a/src/demo_tezos_head/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_head/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_head/configs/dipdup.swarm.yaml b/src/demo_tezos_head/configs/dipdup.swarm.yaml index 3ac8e209b..ea6defb04 100644 --- a/src/demo_tezos_head/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_head/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_head/deploy/compose.mcp.yaml b/src/demo_tezos_head/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_head/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_nft_marketplace/Makefile b/src/demo_tezos_nft_marketplace/Makefile index fe5375ea1..9a5bbead0 100644 --- a/src/demo_tezos_nft_marketplace/Makefile +++ b/src/demo_tezos_nft_marketplace/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_nft_marketplace/configs/dipdup.compose.yaml b/src/demo_tezos_nft_marketplace/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_nft_marketplace/configs/dipdup.compose.yaml +++ b/src/demo_tezos_nft_marketplace/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_nft_marketplace/configs/dipdup.sqlite.yaml b/src/demo_tezos_nft_marketplace/configs/dipdup.sqlite.yaml index 4ddb8a731..fe0d9f7d5 100644 --- a/src/demo_tezos_nft_marketplace/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_nft_marketplace/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_nft_marketplace/configs/dipdup.swarm.yaml b/src/demo_tezos_nft_marketplace/configs/dipdup.swarm.yaml index 49f7424fc..3c9372543 100644 --- a/src/demo_tezos_nft_marketplace/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_nft_marketplace/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_nft_marketplace/deploy/compose.mcp.yaml b/src/demo_tezos_nft_marketplace/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_nft_marketplace/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_raw/Makefile b/src/demo_tezos_raw/Makefile index dfa7bccac..b8f0662a1 100644 --- a/src/demo_tezos_raw/Makefile +++ b/src/demo_tezos_raw/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_raw/configs/dipdup.compose.yaml b/src/demo_tezos_raw/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_raw/configs/dipdup.compose.yaml +++ b/src/demo_tezos_raw/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_raw/configs/dipdup.sqlite.yaml b/src/demo_tezos_raw/configs/dipdup.sqlite.yaml index 5b239d9ae..de4f30bd2 100644 --- a/src/demo_tezos_raw/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_raw/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_raw/configs/dipdup.swarm.yaml b/src/demo_tezos_raw/configs/dipdup.swarm.yaml index 0bbc02845..180aa4c2f 100644 --- a/src/demo_tezos_raw/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_raw/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_raw/deploy/compose.mcp.yaml b/src/demo_tezos_raw/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_raw/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_token/Makefile b/src/demo_tezos_token/Makefile index 34e406eb9..0a12d993e 100644 --- a/src/demo_tezos_token/Makefile +++ b/src/demo_tezos_token/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_token/configs/dipdup.compose.yaml b/src/demo_tezos_token/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_token/configs/dipdup.compose.yaml +++ b/src/demo_tezos_token/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token/configs/dipdup.sqlite.yaml b/src/demo_tezos_token/configs/dipdup.sqlite.yaml index 6679102e4..1aaf06898 100644 --- a/src/demo_tezos_token/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_token/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token/configs/dipdup.swarm.yaml b/src/demo_tezos_token/configs/dipdup.swarm.yaml index dac6da61c..be4f503d3 100644 --- a/src/demo_tezos_token/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_token/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token/deploy/compose.mcp.yaml b/src/demo_tezos_token/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_token/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_token_balances/Makefile b/src/demo_tezos_token_balances/Makefile index 66f6dfb0c..c8504a028 100644 --- a/src/demo_tezos_token_balances/Makefile +++ b/src/demo_tezos_token_balances/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_token_balances/configs/dipdup.compose.yaml b/src/demo_tezos_token_balances/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_token_balances/configs/dipdup.compose.yaml +++ b/src/demo_tezos_token_balances/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token_balances/configs/dipdup.sqlite.yaml b/src/demo_tezos_token_balances/configs/dipdup.sqlite.yaml index bcaa587a3..e8cf4b407 100644 --- a/src/demo_tezos_token_balances/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_token_balances/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token_balances/configs/dipdup.swarm.yaml b/src/demo_tezos_token_balances/configs/dipdup.swarm.yaml index 704c4305a..c819cb038 100644 --- a/src/demo_tezos_token_balances/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_token_balances/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token_balances/deploy/compose.mcp.yaml b/src/demo_tezos_token_balances/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_token_balances/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/demo_tezos_token_transfers/Makefile b/src/demo_tezos_token_transfers/Makefile index 85b5c7d92..269f174ca 100644 --- a/src/demo_tezos_token_transfers/Makefile +++ b/src/demo_tezos_token_transfers/Makefile @@ -1,3 +1,4 @@ +# generated by DipDup 8.2.2 .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -16,11 +17,11 @@ all: ## Run an entire CI pipeline ## install: ## Install dependencies - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked update: ## Update dependencies - dipdup self update -q - uv sync --all-extras --all-groups + # dipdup self update -q + uv sync --all-extras --all-groups --link-mode symlink format: ## Format with all tools make black diff --git a/src/demo_tezos_token_transfers/configs/dipdup.compose.yaml b/src/demo_tezos_token_transfers/configs/dipdup.compose.yaml index 02550b348..5a23eabaa 100644 --- a/src/demo_tezos_token_transfers/configs/dipdup.compose.yaml +++ b/src/demo_tezos_token_transfers/configs/dipdup.compose.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token_transfers/configs/dipdup.sqlite.yaml b/src/demo_tezos_token_transfers/configs/dipdup.sqlite.yaml index b9e75120b..9a4aac2e0 100644 --- a/src/demo_tezos_token_transfers/configs/dipdup.sqlite.yaml +++ b/src/demo_tezos_token_transfers/configs/dipdup.sqlite.yaml @@ -8,6 +8,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token_transfers/configs/dipdup.swarm.yaml b/src/demo_tezos_token_transfers/configs/dipdup.swarm.yaml index a7f2190ea..9b05130ed 100644 --- a/src/demo_tezos_token_transfers/configs/dipdup.swarm.yaml +++ b/src/demo_tezos_token_transfers/configs/dipdup.swarm.yaml @@ -19,6 +19,12 @@ sentry: prometheus: host: 0.0.0.0 + port: 8000 api: - host: 0.0.0.0 \ No newline at end of file + host: 0.0.0.0 + port: 46339 + +mcp: + host: 0.0.0.0 + port: 9999 \ No newline at end of file diff --git a/src/demo_tezos_token_transfers/deploy/compose.mcp.yaml b/src/demo_tezos_token_transfers/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/demo_tezos_token_transfers/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: diff --git a/src/dipdup/projects/base/Makefile.j2 b/src/dipdup/projects/base/Makefile.j2 index 3da7103eb..003fdd660 100644 --- a/src/dipdup/projects/base/Makefile.j2 +++ b/src/dipdup/projects/base/Makefile.j2 @@ -1,3 +1,4 @@ +{{ header }} .PHONY: $(MAKECMDGOALS) MAKEFLAGS += --no-print-directory ## @@ -17,7 +18,7 @@ all: ## Run an entire CI pipeline install: ## Install dependencies {%- if project.package_manager == 'uv' %} - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink --locked {%- elif project.package_manager == 'poetry' %} poetry install {%- elif project.package_manager == 'pdm' %} @@ -27,15 +28,13 @@ install: ## Install dependencies {% endif %} update: ## Update dependencies - dipdup self update -q + # dipdup self update -q {%- if project.package_manager == 'uv' %} - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --link-mode symlink {%- elif project.package_manager == 'poetry' %} poetry update - dipdup self update -q {%- elif project.package_manager == 'pdm' %} pdm update - dipdup self update -q {%- elif project.package_manager == 'none' %} true {% endif %} diff --git a/src/dipdup/projects/base/deploy/compose.mcp.yaml b/src/dipdup/projects/base/deploy/compose.mcp.yaml new file mode 100644 index 000000000..4fc09271b --- /dev/null +++ b/src/dipdup/projects/base/deploy/compose.mcp.yaml @@ -0,0 +1,67 @@ +name: {{ project.package }} + +services: + dipdup: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 46339 + - 9000 + command: ["-C", "compose", "run"] + depends_on: + - db + - hasura + + db: + image: {{ project.postgres_image }} + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + volumes: + - db:{{ project.postgres_data_path }} + restart: always + env_file: .env + environment: + - POSTGRES_USER=dipdup + - POSTGRES_DB=dipdup + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dipdup"] + interval: 10s + timeout: 5s + retries: 5 + + hasura: + image: {{ project.hasura_image }} + ports: + - "${HASURA_HOST_PORT:-8080}:8080" + depends_on: + - db + restart: always + environment: + - HASURA_GRAPHQL_DATABASE_URL=postgres://dipdup:${POSTGRES_PASSWORD}@db:5432/dipdup + - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET} + - HASURA_GRAPHQL_ENABLE_CONSOLE=true + - HASURA_GRAPHQL_DEV_MODE=true + - HASURA_GRAPHQL_LOG_LEVEL=info + - HASURA_GRAPHQL_ENABLE_TELEMETRY=false + - HASURA_GRAPHQL_UNAUTHORIZED_ROLE=user + - HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true + + mcp: + build: + context: .. + dockerfile: deploy/Dockerfile + restart: always + env_file: .env + ports: + - 9999 + command: ["-C", "compose", "mcp", "run"] + depends_on: + - db + - hasura + +volumes: + db: From 728fc5f54d240b5af4b955966420ca74981d5618 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Mar 2025 13:51:11 -0300 Subject: [PATCH 24/30] docs --- docs/5.advanced/7.mcp.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index e2e592ec8..b0a6a2a52 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -92,6 +92,23 @@ async def tool_holders() -> str: In this example, we define a new tool `TopHolders` that returns the top 10 token holders sorted by their balance. +### Using application context + +You can use the application context the same way as in handlers and hooks: + +```python +from dipdup import mcp +from dipdup.context import DipDupContext + +ctx: DipDupContext = mcp.get_ctx() + +ctx.logger.info('Hello from MCP tool!') +``` + +### Interacting with running indexer + +TBD + ### Debugging To check if your tool is working correctly before using it in the client, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) app. From d25379b94f5e554ca1dd6b6f6d0129b5cdd18085 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Mar 2025 14:16:56 -0300 Subject: [PATCH 25/30] ctx refactoring --- docs/5.advanced/7.mcp.md | 9 +++--- src/dipdup/cli.py | 18 ++++++++---- src/dipdup/context.py | 61 ++++++++++++++++++++++++++++++++++++---- src/dipdup/mcp.py | 34 +++++++++++++--------- 4 files changed, 93 insertions(+), 29 deletions(-) diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index b0a6a2a52..4c5bf43bb 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -94,15 +94,16 @@ In this example, we define a new tool `TopHolders` that returns the top 10 token ### Using application context -You can use the application context the same way as in handlers and hooks: +You can use the application context the same way as in handlers and hooks. Use the `mcp.get_ctx()` function to get the context object. ```python from dipdup import mcp -from dipdup.context import DipDupContext +from dipdup.context import McpContext -ctx: DipDupContext = mcp.get_ctx() +async def tool(): + ctx: McpContext = mcp.get_ctx() -ctx.logger.info('Hello from MCP tool!') + ctx.logger.info('Hello from MCP tool!') ``` ### Interacting with running indexer diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 9392e0fd0..f7adfb90b 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -553,18 +553,24 @@ async def mcp_run(ctx: click.Context) -> None: from dipdup import mcp from dipdup.config import DipDupConfig + from dipdup.context import McpContext from dipdup.dipdup import DipDup config: DipDupConfig = ctx.obj.config dipdup = DipDup(config) + if not config.mcp: + config.mcp = McpConfig() mcp_config = config.mcp - if not mcp_config: - mcp_config = McpConfig() - mcp.set_ctx(dipdup._ctx) + mcp_ctx = McpContext._wrap( + ctx=dipdup._ctx, + logger=mcp._logger, + server=mcp.server, + ) + mcp._set_ctx(mcp_ctx) - # NOTE: Import all submodules to find @mcp.tool decorators + # NOTE: Import all submodules to find @mcp decorators dipdup._ctx.package.verify() # NOTE: Run MCP in a separate thread to avoid blocking the DB connection @@ -577,10 +583,10 @@ async def mcp_run(ctx: click.Context) -> None: async def handle_sse(request: Any) -> None: async with sse.connect_sse(request.scope, request.receive, request._send) as streams: - await mcp._app.run( + await mcp.server.run( read_stream=streams[0], write_stream=streams[1], - initialization_options=mcp._app.create_initialization_options(), + initialization_options=mcp.server.create_initialization_options(), raise_exceptions=False, ) diff --git a/src/dipdup/context.py b/src/dipdup/context.py index 6ac732a7c..7c18a69ec 100644 --- a/src/dipdup/context.py +++ b/src/dipdup/context.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Literal +from typing import Self from typing import TypeVar from tortoise.exceptions import OperationalError @@ -102,6 +103,8 @@ from collections.abc import Iterator from types import ModuleType + from mcp.server import Server as McpServer + from dipdup.package import DipDupPackage from dipdup.transactions import TransactionManager @@ -145,7 +148,7 @@ class DipDupContext: :param config: DipDup configuration :param package: DipDup package :param datasources: Mapping of available datasources - :param transactions: Transaction manager (don't use it directly) + :param transactions: Transaction manager (low-level interface) :param logger: Context-aware logger instance """ @@ -788,7 +791,7 @@ class HookContext(DipDupContext): :param config: DipDup configuration :param package: DipDup package :param datasources: Mapping of available datasources - :param transactions: Transaction manager (don't use it directly) + :param transactions: Transaction manager (low-level interface) :param logger: Context-aware logger instance :param hook_config: Configuration of the current hook """ @@ -820,7 +823,7 @@ def _wrap( ctx: DipDupContext, logger: Logger, hook_config: HookConfig, - ) -> HookContext: + ) -> Self: new_ctx = cls( config=ctx.config, package=ctx.package, @@ -851,7 +854,7 @@ class HandlerContext(DipDupContext): :param config: DipDup configuration :param package: DipDup package :param datasources: Mapping of available datasources - :param transactions: Transaction manager (don't use it directly) + :param transactions: Transaction manager (low-level interface) :param logger: Context-aware logger instance :param handler_config: Configuration of the current handler """ @@ -887,7 +890,7 @@ def _wrap( ctx: DipDupContext, logger: Logger, handler_config: HandlerConfig, - ) -> HandlerContext: + ) -> Self: new_ctx = cls( config=ctx.config, package=ctx.package, @@ -903,3 +906,51 @@ def _wrap( def is_finalized(self) -> bool: # FIXME: check the datasource return True + + +class McpContext(DipDupContext): + """Execution context of MCP tools, resources and prompts. + + :param config: DipDup configuration + :param package: DipDup package + :param datasources: Mapping of available datasources + :param transactions: Transaction manager (low-level interface) + :param logger: Context-aware logger instance + :param server: Running MCP server instance + """ + + def __init__( + self, + config: DipDupConfig, + package: DipDupPackage, + datasources: dict[str, Datasource[Any]], + transactions: TransactionManager, + logger: Logger, + server: McpServer[Any], + ) -> None: + super().__init__( + config=config, + package=package, + datasources=datasources, + transactions=transactions, + ) + self.logger = logger + self.server = server + + @classmethod + def _wrap( + cls, + ctx: DipDupContext, + logger: Logger, + server: Any, + ) -> Self: + new_ctx = cls( + config=ctx.config, + package=ctx.package, + datasources=ctx.datasources, + transactions=ctx.transactions, + logger=logger, + server=server, + ) + ctx._link(new_ctx) + return new_ctx diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index 386d7fb17..50370b1b8 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -8,12 +8,13 @@ from pydantic import AnyUrl from dipdup import models -from dipdup.context import DipDupContext +from dipdup.context import McpContext +from dipdup.exceptions import FrameworkException from dipdup.utils import json_dumps _logger = logging.getLogger(__name__) -_ctx: DipDupContext | None = None +_ctx: McpContext | None = None import mcp.server import mcp.types as types @@ -108,19 +109,21 @@ async def _resource_indexes() -> list[dict[str, Any]]: # NOTE: Context management -def get_ctx() -> DipDupContext: +def get_ctx() -> McpContext: global _ctx if _ctx is None: - raise ValueError('DipDup context is not initialized') + raise FrameworkException('DipDup context is not initialized') return _ctx -def set_ctx(ctx: DipDupContext) -> None: +def _set_ctx(ctx: McpContext) -> None: global _ctx + if _ctx is not None: + raise FrameworkException('DipDup context is already initialized') _ctx = ctx -_app: mcp.server.Server[Any] = mcp.server.Server(name='DipDup') +server: mcp.server.Server[Any] = mcp.server.Server(name='DipDup') _user_tools: dict[str, types.Tool] = {} _user_tools_fn: dict[str, Callable[..., Awaitable[Iterable[str]]]] = {} _user_resources: dict[str, types.Resource] = {} @@ -128,7 +131,7 @@ def set_ctx(ctx: DipDupContext) -> None: # TODO: Push typehints to upstream -@_app.list_tools() # type: ignore[no-untyped-call,misc] +@server.list_tools() # type: ignore[no-untyped-call,misc] async def list_tools() -> list[types.Tool]: return [ *list(DIPDUP_TOOLS.values()), @@ -136,7 +139,7 @@ async def list_tools() -> list[types.Tool]: ] -@_app.list_resources() # type: ignore[no-untyped-call,misc] +@server.list_resources() # type: ignore[no-untyped-call,misc] async def list_resources() -> list[types.Resource]: return [ *list(DIPDUP_RESOURCES.values()), @@ -145,12 +148,12 @@ async def list_resources() -> list[types.Resource]: # FIXME: Not supported -@_app.list_resource_templates() # type: ignore[no-untyped-call,misc] +@server.list_resource_templates() # type: ignore[no-untyped-call,misc] async def list_resource_templates() -> list[types.ResourceTemplate]: return [] -@_app.call_tool() # type: ignore[no-untyped-call,misc] +@server.call_tool() # type: ignore[no-untyped-call,misc] async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.TextContent]: if name in _user_tools_fn: res = await _user_tools_fn[name](**arguments) @@ -163,7 +166,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.TextCont raise NotImplementedError(name) -@_app.read_resource() # type: ignore[no-untyped-call,misc] +@server.read_resource() # type: ignore[no-untyped-call,misc] async def read_resource(uri: AnyUrl) -> str: if uri.scheme != 'dipdup': @@ -175,7 +178,8 @@ async def read_resource(uri: AnyUrl) -> str: elif name in DIPDUP_RESOURCES_FN: res = await DIPDUP_RESOURCES_FN[name]() else: - raise NotImplementedError(name) + msg = f'Resource `{name}` not found' + raise FrameworkException(msg) # FIXME: mimeType is always `text/plain` return json_dumps(res, None).decode() @@ -187,7 +191,8 @@ def wrapper(func: Any) -> Any: global _user_tools_fn if name in _user_tools or name in DIPDUP_TOOLS: - raise ValueError(f'Tool `{name}` is already registered') + msg = f'Tool `{name}` is already registered' + raise FrameworkException(msg) _user_tools[name] = types.Tool( name=name, @@ -208,7 +213,8 @@ def wrapper(func: Any) -> Any: global _user_resources_fn if name in _user_resources or name in DIPDUP_RESOURCES: - raise ValueError(f'Resource `{name}` is already registered') + msg = f'Resource `{name}` is already registered' + raise FrameworkException(msg) _user_resources[name] = types.Resource( uri=AnyUrl(f'dipdup://{name}'), From e43a4dc8c09609f57a4ba6af4b1018dd34a61042 Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Thu, 20 Mar 2025 14:22:14 -0300 Subject: [PATCH 26/30] fix inputSchema --- docs/7.references/3.context.md | 6 +++--- src/dipdup/mcp.py | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/7.references/3.context.md b/docs/7.references/3.context.md index 2f286b646..8d40e250d 100644 --- a/docs/7.references/3.context.md +++ b/docs/7.references/3.context.md @@ -18,7 +18,7 @@ description: "Context reference"
                    • config (DipDupConfig) – DipDup configuration

                    • package (DipDupPackage) – DipDup package

                    • datasources (dict[str, Datasource[Any]]) – Mapping of available datasources

                    • -
                    • transactions (TransactionManager) – Transaction manager (don’t use it directly)

                    • +
                    • transactions (TransactionManager) – Transaction manager (low-level interface)

                    • logger – Context-aware logger instance

                    @@ -37,7 +37,7 @@ description: "Context reference"
                  • config (DipDupConfig) – DipDup configuration

                  • package (DipDupPackage) – DipDup package

                  • datasources (dict[str, Datasource[Any]]) – Mapping of available datasources

                  • -
                  • transactions (TransactionManager) – Transaction manager (don’t use it directly)

                  • +
                  • transactions (TransactionManager) – Transaction manager (low-level interface)

                  • logger (Logger) – Context-aware logger instance

                  • handler_config (HandlerConfig) – Configuration of the current handler

                  @@ -57,7 +57,7 @@ description: "Context reference"
                • config (DipDupConfig) – DipDup configuration

                • package (DipDupPackage) – DipDup package

                • datasources (dict[str, Datasource[Any]]) – Mapping of available datasources

                • -
                • transactions (TransactionManager) – Transaction manager (don’t use it directly)

                • +
                • transactions (TransactionManager) – Transaction manager (low-level interface)

                • logger (Logger) – Context-aware logger instance

                • hook_config (HookConfig) – Configuration of the current hook

                diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index 50370b1b8..738baee28 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -194,11 +194,14 @@ def wrapper(func: Any) -> Any: msg = f'Tool `{name}` is already registered' raise FrameworkException(msg) + from mcp.server.fastmcp.tools.base import Tool + + tool_info = Tool.from_function(func, name=name, description=description) + _user_tools[name] = types.Tool( name=name, description=description, - # FIXME: Generate schema from signature - inputSchema={'type': 'object'}, + inputSchema=tool_info.parameters, ) _user_tools_fn[name] = func From c5424639436788b0ea86d038ec7fad5e9816b8be Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Fri, 21 Mar 2025 15:12:52 -0300 Subject: [PATCH 27/30] docs --- docs/5.advanced/7.mcp.md | 41 +++++++++++++++++++++++++++++++++++----- src/dipdup/mcp.py | 1 + 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index 4c5bf43bb..cc5bd401e 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -5,17 +5,27 @@ description: "MCP is an open protocol that standardizes how applications provide # MCP integration +::banner{type="warning"} +**You are early!** + +MCP is a pretty recent technology, so many tools might not work as expected. Consult the [Known issues](#known-issues) section for more information and open the ticket in [GitHub issues](https://github.com/dipdup-io/dipdup/issues) if you encounter any problems. +:: + ## Introduction The **Model Context Protocol** (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Whether you're building an AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to connect LLMs with the context they need. -**DipDup** provides an MCP server with several built-in resources to help LLMs understand the context of your project and the current state of the indexer. +There are three types of MCP primitives (citing from the docs): -You can also implement your own tools and resources specific to your project. +- **Resources** are **application-controlled** and allow to expose data and content that can be read by clients and used as context for LLM interactions. +- **Tools** are **model-controlled** and enable servers to expose executable functionality to clients, such as interacting with external systems, performing computations, and taking actions in the real world. +- **Prompts** are **user-controlled** enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs + +**DipDup** provides an MCP server with several built-in primitives to help LLMs understand the context of your project and the current state of the indexer. You can also implement your own tools and resources specific to your project. ## Running MCP server -DipDup MCP server runs in a separate process. To start it, run the following command: +DipDup MCP server runs in a separate process via the SSE transport. To start it, run the following command: ```shell dipdup mcp run @@ -106,9 +116,22 @@ async def tool(): ctx.logger.info('Hello from MCP tool!') ``` +### Calling other callbacks + +You can use the `dipdup.mcp` functions to read other resources or call tools from your callbacks. + +```python +from dipdup import mcp + +async def tool(): + result = await mcp.call_tool('my_tool', {}) +``` + +For a low-level access you can use `dipdup.mcp.server` singleton to interact with the running server. + ### Interacting with running indexer -TBD +TBD ### Debugging @@ -123,9 +146,17 @@ logging: '': DEBUG ``` +## Known issues + +- **DipDup** doesn't support custom prompts at the moment. +- **Cursor** fails to discover resources exposed by DipDup server. Tools work fine. +- **VSCode** community extensions for MCP don't work out-of-the-box. +- **Claude Desktop** doesn't support SSE-based MCP servers; gateway tools are required. + ## Further reading - [For Server Developers - Model Context Protocol](https://modelcontextprotocol.io/quickstart/server) - [Example Servers - Model Context Protocol](https://modelcontextprotocol.io/examples) -- [qpd-v/mcp-guide](https://github.com/qpd-v/mcp-guide) - a tutorial server that helps users understand MCP concepts, provides interactive examples, and demonstrates best practices for building MCP integrations. Also, an awesome-list of related projects. +- [qpd-v/mcp-guide](https://github.com/qpd-v/mcp-guide) - a tutorial server that helps users understand MCP concepts, provides interactive examples, and demonstrates best practices for building MCP integrations. Contains [awesome-list](https://raw.githubusercontent.com/qpd-v/mcp-guide/refs/heads/main/src/servers/serverlist.txt) of related projects. - [anjor/coinmarket-mcp-server](https://github.com/anjor/coinmarket-mcp-server) - example MCP server for CoinMarketCap API. +- [ample-education/cursor-resources](https://github.com/ample-education/cursor-resources) - a collection of resources to maximize productivity with Cursor: prompts, rules, MCP servers etc. diff --git a/src/dipdup/mcp.py b/src/dipdup/mcp.py index 738baee28..137f670d5 100644 --- a/src/dipdup/mcp.py +++ b/src/dipdup/mcp.py @@ -123,6 +123,7 @@ def _set_ctx(ctx: McpContext) -> None: _ctx = ctx +# TODO: Add instructions server: mcp.server.Server[Any] = mcp.server.Server(name='DipDup') _user_tools: dict[str, types.Tool] = {} _user_tools_fn: dict[str, Callable[..., Awaitable[Iterable[str]]]] = {} From 9ba9e410e59ef0b2efefaab2346b5ce003e8c5ee Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Fri, 21 Mar 2025 15:35:00 -0300 Subject: [PATCH 28/30] docs and package section --- docs/5.advanced/7.mcp.md | 69 +++++++++++++++++++----- src/demo_blank/mcp/.keep | 0 src/demo_evm_events/mcp/.keep | 0 src/demo_evm_transactions/mcp/.keep | 0 src/demo_evm_uniswap/mcp/.keep | 0 src/demo_starknet_events/mcp/.keep | 0 src/demo_substrate_events/mcp/.keep | 0 src/demo_tezos_auction/mcp/.keep | 0 src/demo_tezos_dao/mcp/.keep | 0 src/demo_tezos_dex/mcp/.keep | 0 src/demo_tezos_domains/mcp/.keep | 0 src/demo_tezos_events/mcp/.keep | 0 src/demo_tezos_factories/mcp/.keep | 0 src/demo_tezos_head/mcp/.keep | 0 src/demo_tezos_nft_marketplace/mcp/.keep | 0 src/demo_tezos_raw/mcp/.keep | 0 src/demo_tezos_token/mcp/.keep | 0 src/demo_tezos_token_balances/mcp/.keep | 0 src/demo_tezos_token_transfers/mcp/.keep | 0 src/dipdup/package.py | 3 ++ 20 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 src/demo_blank/mcp/.keep create mode 100644 src/demo_evm_events/mcp/.keep create mode 100644 src/demo_evm_transactions/mcp/.keep create mode 100644 src/demo_evm_uniswap/mcp/.keep create mode 100644 src/demo_starknet_events/mcp/.keep create mode 100644 src/demo_substrate_events/mcp/.keep create mode 100644 src/demo_tezos_auction/mcp/.keep create mode 100644 src/demo_tezos_dao/mcp/.keep create mode 100644 src/demo_tezos_dex/mcp/.keep create mode 100644 src/demo_tezos_domains/mcp/.keep create mode 100644 src/demo_tezos_events/mcp/.keep create mode 100644 src/demo_tezos_factories/mcp/.keep create mode 100644 src/demo_tezos_head/mcp/.keep create mode 100644 src/demo_tezos_nft_marketplace/mcp/.keep create mode 100644 src/demo_tezos_raw/mcp/.keep create mode 100644 src/demo_tezos_token/mcp/.keep create mode 100644 src/demo_tezos_token_balances/mcp/.keep create mode 100644 src/demo_tezos_token_transfers/mcp/.keep diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index cc5bd401e..b9c6360bd 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -84,9 +84,11 @@ There is also [lightconetech/mcp-gateway](https://github.com/lightconetech/mcp-g To make your server more useful, you can implement custom MCP primitives specific to your project. For example, let's implement a tool that provides some information about token holders. -Create a new file `mcp.py` in the `handlers` directory: +If you used DipDup prior to 8.3 release, run `dipdup init` command to update the project structure. -```python [handlers/mcp.py] +Create a new file `tools.py` in the `mcp` project directory. In this example, we define a new tool `TopHolders` that returns the top 10 token holders sorted by their balance. + +```python [mcp/tools.py] from dipdup import mcp from demo_evm_events import models @@ -94,14 +96,12 @@ from demo_evm_events import models @mcp.tool('TopHolders', 'Get top token holders') async def tool_holders() -> str: holders = await models.Holder.filter().order_by('-balance').limit(10).all() - res = 'Top holders:\n' + res = 'Top holders:\n\n' for holder in holders: - res += f'{holder.address}: {holder.balance}\n' + res += f'- {holder.address}: {holder.balance}\n' return res ``` -In this example, we define a new tool `TopHolders` that returns the top 10 token holders sorted by their balance. - ### Using application context You can use the application context the same way as in handlers and hooks. Use the `mcp.get_ctx()` function to get the context object. @@ -110,10 +110,11 @@ You can use the application context the same way as in handlers and hooks. Use t from dipdup import mcp from dipdup.context import McpContext +@mcp.tool(...) async def tool(): - ctx: McpContext = mcp.get_ctx() + ctx: McpContext = mcp.get_ctx() - ctx.logger.info('Hello from MCP tool!') + ctx.logger.info('Hello from MCP tool!') ``` ### Calling other callbacks @@ -123,23 +124,63 @@ You can use the `dipdup.mcp` functions to read other resources or call tools fro ```python from dipdup import mcp +@mcp.tool(...) async def tool(): - result = await mcp.call_tool('my_tool', {}) + result = await mcp.call_tool('my_tool', {}) ``` For a low-level access you can use `dipdup.mcp.server` singleton to interact with the running server. ### Interacting with running indexer -TBD +DipDup provides [management API](../7.references/4.api.md) to interact with the running indexer. For example you can use it to add indexes in runtime. First, add running indexer as a HTTP datasource: + +```yaml [dipdup.yaml] +datasources: + indexer: + kind: http + # NOTE: Default for Compose stack + url: http://api:46339 +``` + +Then, call this datasource from your MCP tool: + +```python +from dipdup import mcp + +@mcp.tool(...) +async def tool(): + ctx = mcp.get_ctx() + datasource = ctx.get_http_datasource('indexer') + response = await datasource.post( + '/add_index', + params={ + 'name': 'my_index', + 'template': 'my_template', + 'values': {'param': 'value'}, + 'first_level': 0, + 'last_level': 1000, + } + ) +``` + +## Running in Docker + +You can find a Compose file in `deploy/` project directory that runs both the indexer and the MCP server in Docker containers. To use this manifest, use the following command: + +```shell [Terminal] +make up COMPOSE=deploy/compose.mcp.yaml +``` -### Debugging +### Debugging and troubleshooting -To check if your tool is working correctly before using it in the client, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) app. +To check if your tool is working correctly before using it in the client, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) app: -Run `npx @modelcontextprotocol/inspector`, open `http://127.0.0.1:5173/` in your browser, choose SSE transport and connect to `http://127.0.0.1:9999/sse`. +1. Run `npx @modelcontextprotocol/inspector` +2. Open `http://127.0.0.1:5173/` in your browser +3. Choose SSE transport and connect to `http://127.0.0.1:9999/sse`. -If you suspect there's a bug in the framework, you can enable full logging to get some insights into the MCP server: +You can also enable full logging to get some insights into the MCP server: ```yaml [dipdup.yaml] logging: diff --git a/src/demo_blank/mcp/.keep b/src/demo_blank/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_evm_events/mcp/.keep b/src/demo_evm_events/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_evm_transactions/mcp/.keep b/src/demo_evm_transactions/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_evm_uniswap/mcp/.keep b/src/demo_evm_uniswap/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_starknet_events/mcp/.keep b/src/demo_starknet_events/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_substrate_events/mcp/.keep b/src/demo_substrate_events/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_auction/mcp/.keep b/src/demo_tezos_auction/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_dao/mcp/.keep b/src/demo_tezos_dao/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_dex/mcp/.keep b/src/demo_tezos_dex/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_domains/mcp/.keep b/src/demo_tezos_domains/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_events/mcp/.keep b/src/demo_tezos_events/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_factories/mcp/.keep b/src/demo_tezos_factories/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_head/mcp/.keep b/src/demo_tezos_head/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_nft_marketplace/mcp/.keep b/src/demo_tezos_nft_marketplace/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_raw/mcp/.keep b/src/demo_tezos_raw/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_token/mcp/.keep b/src/demo_tezos_token/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_token_balances/mcp/.keep b/src/demo_tezos_token_balances/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_token_transfers/mcp/.keep b/src/demo_tezos_token_transfers/mcp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/dipdup/package.py b/src/dipdup/package.py index 8e2b400ee..a77e84e02 100644 --- a/src/dipdup/package.py +++ b/src/dipdup/package.py @@ -77,6 +77,7 @@ def __init__(self, root: Path, quiet: bool = False) -> None: self.models = root / 'models' self.sql = root / 'sql' self.types = root / 'types' + self.mcp = root / 'mcp' # NOTE: Optional, created if aerich is installed self.migrations = root / 'migrations' @@ -128,6 +129,7 @@ def skel(self) -> dict[Path, str | None]: self.models: '**/*.py', self.sql: '**/*.sql', self.types: '**/*.py', + self.mcp: '**/*.py', # NOTE: Python metadata Path(PEP_561_MARKER): None, } @@ -197,6 +199,7 @@ def verify(self) -> None: import_submodules(f'{self.name}.handlers') import_submodules(f'{self.name}.hooks') import_submodules(f'{self.name}.types') + import_submodules(f'{self.name}.mcp') def get_type(self, typename: str, module: str, name: str) -> type[BaseModel]: key = f'{typename}{module}{name}' From b6e27da99c9c0b9c6a00a0ea834c1004a8e2923a Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Fri, 21 Mar 2025 15:38:48 -0300 Subject: [PATCH 29/30] typo, refs` --- docs/5.advanced/7.mcp.md | 2 +- docs/7.references/3.context.md | 20 ++++++++++++++++++++ docs/context.rst | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index b9c6360bd..6492fc747 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -65,7 +65,7 @@ If your server is running, but Cursor doesn't connect, try "Reload Window" in th There is no official support for MCP in VSCode yet. -There are two extensions available in repository to add MCP capabilities to Copilot. None of them worked out-of-the-box at the time of writing this document. If have better luck, please let us know in GitHub issues. +There are two extensions available in repository to add MCP capabilities to Copilot. None of them worked out-of-the-box at the time of writing this document. If you have better luck, please let us know in GitHub issues. - [Copilot MCP](https://marketplace.visualstudio.com/items?itemName=AutomataLabs.copilot-mcp) - [MCP-Client](https://marketplace.visualstudio.com/items?itemName=m1self.mcp-client) diff --git a/docs/7.references/3.context.md b/docs/7.references/3.context.md index 8d40e250d..b5c9b0b09 100644 --- a/docs/7.references/3.context.md +++ b/docs/7.references/3.context.md @@ -65,6 +65,26 @@ description: "Context reference"
              +
              + +## dipdup.context.McpContext + +class dipdup.context.McpContext(config, package, datasources, transactions, logger, server) +

              Execution context of MCP tools, resources and prompts.

              +
              +
              Parameters:
              +
                +
              • config (DipDupConfig) – DipDup configuration

              • +
              • package (DipDupPackage) – DipDup package

              • +
              • datasources (dict[str, Datasource[Any]]) – Mapping of available datasources

              • +
              • transactions (TransactionManager) – Transaction manager (low-level interface)

              • +
              • logger (Logger) – Context-aware logger instance

              • +
              • server (McpServer[Any]) – Running MCP server instance

              • +
              +
              +
              +
              +
              ## dipdup.context.DipDupContext.add_contract diff --git a/docs/context.rst b/docs/context.rst index d0183170e..013204e35 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -3,6 +3,7 @@ .. autoclass:: dipdup.context.DipDupContext .. autoclass:: dipdup.context.HandlerContext .. autoclass:: dipdup.context.HookContext +.. autoclass:: dipdup.context.McpContext .. automethod:: dipdup.context.DipDupContext.add_contract .. automethod:: dipdup.context.DipDupContext.add_index From 019cf49f983091139899942c8e6042a6e85a3e6e Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Sat, 22 Mar 2025 13:14:48 -0300 Subject: [PATCH 30/30] docs --- docs/1.getting-started/4.package.md | 1 + docs/5.advanced/7.mcp.md | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/1.getting-started/4.package.md b/docs/1.getting-started/4.package.md index 880591377..1403c3c0e 100644 --- a/docs/1.getting-started/4.package.md +++ b/docs/1.getting-started/4.package.md @@ -21,6 +21,7 @@ The structure of the resulting package is the following: | :file_folder: `hasura` | Arbitrary Hasura metadata to apply during configuration | | :file_folder: `hooks` | User-defined callbacks to run manually or by schedule | | :file_folder: `models` | DipDup ORM models to store data in the database | +| :file_folder: `mcp` | Custom MCP rools and resources | | :file_folder: `sql` | SQL scripts and queries to run manually or on specific events | | :file_folder: `types` | Automatically generated Pydantic dataclasses for contract types | | `dipdup.yaml` | Root DipDup config; can be expanded with env-specific files | diff --git a/docs/5.advanced/7.mcp.md b/docs/5.advanced/7.mcp.md index 6492fc747..756ed0f4b 100644 --- a/docs/5.advanced/7.mcp.md +++ b/docs/5.advanced/7.mcp.md @@ -19,7 +19,7 @@ There are three types of MCP primitives (citing from the docs): - **Resources** are **application-controlled** and allow to expose data and content that can be read by clients and used as context for LLM interactions. - **Tools** are **model-controlled** and enable servers to expose executable functionality to clients, such as interacting with external systems, performing computations, and taking actions in the real world. -- **Prompts** are **user-controlled** enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs +- **Prompts** are **user-controlled** and enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs **DipDup** provides an MCP server with several built-in primitives to help LLMs understand the context of your project and the current state of the indexer. You can also implement your own tools and resources specific to your project. @@ -65,9 +65,9 @@ If your server is running, but Cursor doesn't connect, try "Reload Window" in th There is no official support for MCP in VSCode yet. -There are two extensions available in repository to add MCP capabilities to Copilot. None of them worked out-of-the-box at the time of writing this document. If you have better luck, please let us know in GitHub issues. +There are two extensions available in repository to add MCP capabilities to **Copilot** assistant. None of them worked out-of-the-box at the time of writing this document. -- [Copilot MCP](https://marketplace.visualstudio.com/items?itemName=AutomataLabs.copilot-mcp) +- [Copilot MCP](https://marketplace.visualstudio.com/items?itemName=AutomataLabs.copilot-mcp) (issue [#20](https://github.com/VikashLoomba/copilot-mcp/issues/20)) - [MCP-Client](https://marketplace.visualstudio.com/items?itemName=m1self.mcp-client) ### Claude Desktop @@ -190,7 +190,7 @@ logging: ## Known issues - **DipDup** doesn't support custom prompts at the moment. -- **Cursor** fails to discover resources exposed by DipDup server. Tools work fine. +- **Cursor** fails to discover resources exposed by DipDup server. Tools work fine. (0.47.8 tested) - **VSCode** community extensions for MCP don't work out-of-the-box. - **Claude Desktop** doesn't support SSE-based MCP servers; gateway tools are required.