Skip to content

Commit 6455a29

Browse files
Index templates, generate only required types (#5)
* Add reference config, convert contracts block to dict` * Index templates * Generate required types only, cleanup schemas * Add templates paragraph to readme * Pass template_values to handlers
1 parent 21c097f commit 6455a29

File tree

69 files changed

+419
-603
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+419
-603
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,45 @@ $ # edit `secrets.env` file, change credentials
182182
$ docker-compose up dipdup
183183
```
184184

185+
### Index templates
186+
187+
Sometimes you need to run multiple indexes with similar configs whose only difference is contract addresses. In this case you can use index templates like this:
188+
189+
```yaml
190+
templates:
191+
trades:
192+
kind: operation
193+
datasource: tzkt_staging
194+
contract: < dex >
195+
handlers:
196+
- callback: on_fa12_token_to_tez
197+
pattern:
198+
- destination: < dex >
199+
entrypoint: tokenToTezPayment
200+
- destination: < token >
201+
entrypoint: transfer
202+
- callback: on_fa20_tez_to_token
203+
pattern:
204+
- destination: < dex >
205+
entrypoint: tezToTokenPayment
206+
- destination: < token >
207+
entrypoint: transfer
208+
209+
indexes:
210+
trades_fa12:
211+
template: trades
212+
values:
213+
dex: FA12_dex
214+
token: FA12_token
215+
216+
trades_fa20:
217+
template: trades
218+
values:
219+
dex: FA20_dex
220+
token: FA20_token
221+
```
222+
223+
185224
### Optional: configure Hasura GraphQL Engine
186225
187226
`init` command generates Hasura metadata JSON in the package root. You can use `configure-graphql` command to apply it to the running GraphQL Engine instance:

src/dipdup/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ async def init(ctx):
149149
await codegen.generate_types(config)
150150
await codegen.generate_handlers(config)
151151
await codegen.generate_hasura_metadata(config)
152+
await codegen.cleanup(config)
152153

153154

154155
@cli.command(help='Configure Hasura GraphQL Engine')

src/dipdup/codegen.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from contextlib import suppress
77
from os import mkdir
88
from os.path import dirname, exists, join
9-
from typing import List
9+
from shutil import rmtree
10+
from typing import List, Tuple
1011

1112
from jinja2 import Template
1213
from tortoise import Model, fields
@@ -51,15 +52,15 @@ async def fetch_schemas(config: DipDupConfig):
5152

5253
for contract_name, contract in config.contracts.items():
5354
_logger.info('Fetching schemas for contract `%s`', contract_name)
54-
address_schemas_path = join(schemas_path, contract.address)
55+
address_schemas_path = join(schemas_path, contract)
5556
with suppress(FileExistsError):
5657
mkdir(address_schemas_path)
5758

5859
parameter_schemas_path = join(address_schemas_path, 'parameter')
5960
with suppress(FileExistsError):
6061
mkdir(parameter_schemas_path)
6162

62-
address_schemas_json = await datasource.fetch_jsonschemas(contract.address)
63+
address_schemas_json = await datasource.fetch_jsonschemas(contract)
6364
for entrypoint_json in address_schemas_json['entrypoints']:
6465
entrypoint = entrypoint_json['name']
6566
entrypoint_schema = entrypoint_json['parameterSchema']
@@ -79,6 +80,14 @@ async def generate_types(config: DipDupConfig):
7980
with open(join(types_path, '__init__.py'), 'w'):
8081
pass
8182

83+
_logger.info('Determining required types')
84+
required_types: List[Tuple[str, str, str]] = []
85+
for index_config in config.indexes.values():
86+
if isinstance(index_config, OperationIndexConfig):
87+
for handler_config in index_config.handlers:
88+
for pattern_config in handler_config.pattern:
89+
required_types.append((pattern_config.destination, 'parameter', pattern_config.entrypoint))
90+
8291
for root, dirs, files in os.walk(schemas_path):
8392
types_root = root.replace(schemas_path, types_path)
8493

@@ -95,6 +104,13 @@ async def generate_types(config: DipDupConfig):
95104
entrypoint_name = file[:-5]
96105
entrypoint_name_titled = entrypoint_name.title().replace('_', '')
97106

107+
type_type = types_root.split('/')[-1]
108+
contract = types_root.split('/')[-2]
109+
110+
key = (contract, type_type, entrypoint_name)
111+
if key not in required_types:
112+
continue
113+
98114
input_path = join(root, file)
99115
output_path = join(types_root, f'{entrypoint_name}.py')
100116
_logger.info('Generating parameter type for entrypoint `%s`', entrypoint_name)
@@ -254,3 +270,9 @@ async def generate_hasura_metadata(config: DipDupConfig):
254270
metadata_path = join(config.package_path, 'hasura_metadata.json')
255271
with open(metadata_path, 'w') as file:
256272
json.dump(metadata, file, indent=4)
273+
274+
275+
async def cleanup(config: DipDupConfig):
276+
_logger.info('Cleaning up')
277+
schemas_path = join(config.package_path, 'schemas')
278+
rmtree(schemas_path)

src/dipdup/config.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ class SqliteDatabaseConfig:
2929
:param path: Path to .sqlite3 file, leave default for in-memory database
3030
"""
3131

32+
kind: Literal['sqlite']
3233
path: str = ':memory:'
3334

3435
@property
3536
def connection_string(self):
36-
return f'sqlite://{self.path}'
37+
return f'{self.kind}://{self.path}'
3738

3839

3940
@dataclass
@@ -48,7 +49,7 @@ class DatabaseConfig:
4849
:param database: Schema name
4950
"""
5051

51-
driver: str
52+
kind: Union[Literal['postgres'], Literal['mysql']]
5253
host: str
5354
port: int
5455
user: str
@@ -57,7 +58,7 @@ class DatabaseConfig:
5758

5859
@property
5960
def connection_string(self):
60-
return f'{self.driver}://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}'
61+
return f'{self.kind}://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}'
6162

6263

6364
@dataclass
@@ -144,6 +145,7 @@ class OperationIndexConfig:
144145
def __post_init_post_parse__(self):
145146
self._state: Optional[State] = None
146147
self._rollback_fn: Optional[Callable] = None
148+
self._template_values: Dict[str, str] = None
147149

148150
def hash(self) -> str:
149151
return hashlib.sha256(
@@ -173,6 +175,14 @@ def rollback_fn(self) -> Callable:
173175
def rollback_fn(self, value: Callable) -> None:
174176
self._rollback_fn = value
175177

178+
@property
179+
def template_values(self) -> Optional[Dict[str, str]]:
180+
return self._template_values
181+
182+
@template_values.setter
183+
def template_values(self, value: Dict[str, str]) -> None:
184+
self._template_values = value
185+
176186

177187
@dataclass
178188
class BigmapdiffHandlerPatternConfig:
@@ -208,14 +218,13 @@ class BlockIndexConfig:
208218

209219

210220
@dataclass
211-
class ContractConfig:
212-
"""Contract config
221+
class IndexTemplateConfig:
222+
template: str
223+
values: Dict[str, str]
213224

214-
:param network: Corresponding network alias, only for sanity checks
215-
:param address: Contract address
216-
"""
217225

218-
address: str
226+
IndexConfigT = Union[OperationIndexConfig, BigmapdiffIndexConfig, BlockIndexConfig, IndexTemplateConfig]
227+
IndexConfigTemplateT = Union[OperationIndexConfig, BigmapdiffIndexConfig, BlockIndexConfig]
219228

220229

221230
@dataclass
@@ -232,21 +241,33 @@ class DipDupConfig:
232241

233242
spec_version: str
234243
package: str
235-
contracts: Dict[str, ContractConfig]
244+
contracts: Dict[str, str]
236245
datasources: Dict[str, Union[TzktDatasourceConfig]]
237-
indexes: Dict[str, Union[OperationIndexConfig, BigmapdiffIndexConfig, BlockIndexConfig]]
238-
database: Union[SqliteDatabaseConfig, DatabaseConfig] = SqliteDatabaseConfig()
246+
indexes: Dict[str, IndexConfigT]
247+
templates: Optional[Dict[str, IndexConfigTemplateT]] = None
248+
database: Union[SqliteDatabaseConfig, DatabaseConfig] = SqliteDatabaseConfig(kind='sqlite')
239249

240250
def __post_init_post_parse__(self):
241251
self._logger = logging.getLogger(__name__)
252+
for index_name, index_config in self.indexes.items():
253+
if isinstance(index_config, IndexTemplateConfig):
254+
template = self.templates[index_config.template]
255+
raw_template = json.dumps(template, default=pydantic_encoder)
256+
for key, value in index_config.values.items():
257+
value_regex = r'<[ ]*' + key + r'[ ]*>'
258+
raw_template = re.sub(value_regex, value, raw_template)
259+
json_template = json.loads(raw_template)
260+
self.indexes[index_name] = template.__class__(**json_template)
261+
self.indexes[index_name].template_values = index_config.values
262+
242263
for index_config in self.indexes.values():
243264
if isinstance(index_config, OperationIndexConfig):
244265
if index_config is None:
245266
continue
246-
index_config.contract = self.contracts[index_config.contract].address
267+
index_config.contract = self.contracts[index_config.contract]
247268
for handler in index_config.handlers:
248269
for pattern in handler.pattern:
249-
pattern.destination = self.contracts[pattern.destination].address
270+
pattern.destination = self.contracts[pattern.destination]
250271

251272
@property
252273
def package_path(self) -> str:

src/dipdup/datasources/tzkt/cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def match_operation(self, pattern_config: OperationHandlerPatternConfig, operati
3939

4040
async def process(
4141
self,
42-
callback: Callable[[OperationHandlerConfig, List[OperationData], List[OperationData]], Awaitable[None]],
42+
callback: Callable[[OperationIndexConfig, OperationHandlerConfig, List[OperationData], List[OperationData]], Awaitable[None]],
4343
) -> int:
4444
keys = list(self._operations.keys())
4545
self._logger.info('Matching %s operation groups', len(keys))
@@ -55,7 +55,7 @@ async def process(
5555

5656
if len(matched_operations) == len(handler_config.pattern):
5757
self._logger.info('Handler `%s` matched! %s', handler_config.callback, key)
58-
await callback(handler_config, matched_operations, operations)
58+
await callback(self._index_config, handler_config, matched_operations, operations)
5959
if key in self._operations:
6060
del self._operations[key]
6161

src/dipdup/datasources/tzkt/datasource.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ async def add_subscription(self, address: str, types: Optional[List[str]] = None
231231

232232
async def on_operation_match(
233233
self,
234+
index_config: OperationIndexConfig,
234235
handler_config: OperationHandlerConfig,
235236
matched_operations: List[OperationData],
236237
operations: List[OperationData],
@@ -247,7 +248,7 @@ async def on_operation_match(
247248
)
248249
args.append(context)
249250

250-
await handler_config.callback_fn(*args, operations)
251+
await handler_config.callback_fn(*args, operations, index_config.template_values)
251252

252253
@classmethod
253254
def convert_operation(cls, operation_json: Dict[str, Any]) -> OperationData:

src/dipdup/templates/handler.py.j2

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import Dict, List, Optional
22
from dipdup.models import HandlerContext, OperationData
33

44
from {{ package }}.models import *
@@ -11,5 +11,6 @@ async def {{ handler }}(
1111
{{ pattern.entrypoint }}: HandlerContext[{{ pattern.entrypoint.title().replace('_', '') }}],
1212
{%- endfor %}
1313
operations: List[OperationData],
14+
template_values: Optional[Dict[str, str]] = None,
1415
) -> None:
1516
...

src/dipdup_hic_et_nunc/dipdup-docker.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ spec_version: 0.0.1
22
package: dipdup_hic_et_nunc
33

44
database:
5-
driver: postgres
5+
kind: postgres
66
host: db
77
port: 5432
88
user: dipdup
99
database: dipdup
1010
password: ${POSTGRES_PASSWORD:-changeme}
1111

1212
contracts:
13-
HEN_objkts:
14-
address: ${HEN_OBJKTS:-KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9}
15-
HEN_minter:
16-
address: ${HEN_MINTER:-KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton}
13+
HEN_objkts: ${HEN_OBJKTS:-KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9}
14+
HEN_minter: ${HEN_MINTER:-KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton}
1715

1816
datasources:
1917
tzkt_mainnet:

src/dipdup_hic_et_nunc/dipdup.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ spec_version: 0.0.1
22
package: dipdup_hic_et_nunc
33

44
database:
5+
kind: sqlite
56
path: db.sqlite3
67

78
contracts:
8-
HEN_objkts:
9-
address: ${HEN_OBJKTS:-KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9}
10-
HEN_minter:
11-
address: ${HEN_MINTER:-KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton}
9+
HEN_objkts: ${HEN_OBJKTS:-KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9}
10+
HEN_minter: ${HEN_MINTER:-KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton}
1211

1312
datasources:
1413
tzkt_mainnet:
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
from typing import List
2-
1+
from typing import Dict, List, Optional
32
from dipdup.models import HandlerContext, OperationData
3+
44
from dipdup_hic_et_nunc.models import *
5+
56
from dipdup_hic_et_nunc.types.KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9.parameter.mint_OBJKT import MintObjkt
67
from dipdup_hic_et_nunc.types.KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton.parameter.mint import Mint
78

8-
99
async def on_mint(
1010
mint_OBJKT: HandlerContext[MintObjkt],
1111
mint: HandlerContext[Mint],
1212
operations: List[OperationData],
13+
template_values: Optional[Dict[str, str]] = None,
1314
) -> None:
14-
await Address.get_or_create(address=mint.parameter.address)
15+
...

0 commit comments

Comments
 (0)