Skip to content

Commit

Permalink
tuples in jsonschema
Browse files Browse the repository at this point in the history
  • Loading branch information
droserasprout committed Jan 18, 2025
1 parent 1eaa579 commit d62e178
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 37 deletions.
14 changes: 9 additions & 5 deletions src/dipdup/codegen/substrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from dipdup.package import DipDupPackage
from dipdup.runtimes import SubstrateRuntime
from dipdup.runtimes import extract_args_name
from dipdup.runtimes import extract_tuple_inner_types
from dipdup.runtimes import get_type_key
from dipdup.utils import json_dumps
from dipdup.utils import pascal_to_snake
from dipdup.utils import snake_to_pascal
Expand All @@ -31,8 +33,9 @@ def scale_type_to_jsonschema(
type_registry: dict[str, Any],
type_string: str,
) -> dict[str, Any]:
if type_string in type_registry['types']:
type_def = type_registry['types'][type_string]
type_key = get_type_key(type_string)
if {type_string, type_key} & set(type_registry['types']):
type_def = type_registry['types'][type_key]
if isinstance(type_def, str):
return scale_type_to_jsonschema(type_registry, type_def)
if isinstance(type_def, dict):
Expand Down Expand Up @@ -69,8 +72,9 @@ def scale_type_to_jsonschema(
schema['type'] = 'string'
# FIXME: We need to parse weird values like `Tuple:staging_xcm:v4:location:Locationstaging_xcm:v4:location:Location`; mind the missing delimeters
elif type_string.startswith('Tuple:'):
# inner_types = type_string[6:]
raise NotImplementedError
inner_types = extract_tuple_inner_types(type_string, 0, type_registry)
schema['type'] = 'array'
schema['items'] = [scale_type_to_jsonschema(type_registry, t) for t in inner_types]

elif type_string.startswith('Vec<'):
inner_type = type_string[4:-1]
Expand Down Expand Up @@ -242,7 +246,7 @@ async def _generate_types(self, force: bool = False) -> None:
name = event_name
break
else:
raise Exception(f'Event not found for {typeclass_dir}')
raise Exception(f'Event not found for {typeclass_dir.stem}')

# NOTE: Don't extract from typeclass path! XYK.Sell -> xyk_sell -> XykSellPayload; should be XYKSellPayload.
typeclass_name = f'{snake_to_pascal(name)}Payload'
Expand Down
73 changes: 41 additions & 32 deletions src/dipdup/runtimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,46 @@ def extract_args_name(docs: tuple[str, ...]) -> tuple[str, ...]:
return tuple(arg.strip('\\ ') for arg in slice.split(','))


def get_type_key(type_: str) -> str:
return type_.split(':')[-1].lower()


def extract_tuple_inner_types(type_: str, length: int, registry: dict[str, Any]) -> list[str]:
inner = type_[6:]
inner_types = []

if '<' in inner:
raise NotImplementedError('Cannot parse nested structures in tuples')

# NOTE: Try simple case first, all items have the same type
if length:
inner_item_len = int(len(inner) / length)

for i in range(0, len(type_), inner_item_len):
inner_types.append(inner[i : i + inner_item_len])
inner_types = [i for i in inner_types if i]

# NOTE: Or read inner type until there's a match in the type registry
if len(set(inner_types)) in (0, 1):
inner_types = []
current_type = ''
for i in range(len(inner)):
current_type += inner[i]
cropped_type = get_type_key(current_type)
if cropped_type in registry['types']:
if i < len(inner) - 1 and inner[i + 1] == ':':
continue

# inner_types.append(cropped_type)
inner_types.append(current_type)
current_type = ''

if current_type or not inner_types:
raise NotImplementedError('Cannot parse tuple with mixed types')

return inner_types


@cache
def get_type_registry(name_or_path: str | Path) -> 'RuntimeConfigurationObject':
from scalecodec.type_registry import load_type_registry_preset # type: ignore[import-untyped]
Expand Down Expand Up @@ -178,9 +218,6 @@ def decode_event_args(

payload = {}

def strip_type(v: str) -> str:
return v.split(':')[-1].lower()

def parse(value: Any, type_: str) -> Any:
if isinstance(value, int):
return value
Expand All @@ -190,35 +227,7 @@ def parse(value: Any, type_: str) -> Any:

# FIXME: Tuple type string have neither brackets no delimeters... Could be a Subscan thing, need to check.
if isinstance(value, list) and type_.startswith('Tuple:'):
inner = type_[6:]
inner_item_len = int(len(inner) / len(value))

if '<' in inner:
raise NotImplementedError('Cannot parse nested structures in tuples')

# NOTE: Try simple case first, all items have the same type
inner_types = []
for i in range(0, len(type_), inner_item_len):
inner_types.append(inner[i : i + inner_item_len])
inner_types = [i for i in inner_types if i]

# NOTE: Or read inner type until there's a match in the type registry
if len(set(inner_types)) != 1:
inner_types = []
current_type = ''
for i in range(len(inner)):
current_type += inner[i]
cropped_type = strip_type(current_type)
if cropped_type in self.runtime_config.type_registry['types']:
if i < len(inner) - 1 and inner[i + 1] == ':':
continue

inner_types.append(cropped_type)
current_type = ''

if current_type or not inner_types:
raise NotImplementedError('Cannot parse tuple with mixed types')

inner_types = extract_tuple_inner_types(type_, len(value), registry=self.runtime_config.registry)
return [parse(v, t) for v, t in zip(value, inner_types, strict=True)]

# NOTE: Scale decoder expects vec length at the beginning; Subsquid strips it
Expand Down

0 comments on commit d62e178

Please sign in to comment.