From 51ddd039c4a68ef6beb89205ad9a427a5ed11ebf Mon Sep 17 00:00:00 2001 From: Christophe Vauclair Date: Tue, 1 Nov 2022 11:23:47 -0400 Subject: [PATCH 1/3] fix: bug where null object would break synthetic fields --- subgrounds/transform.py | 28 +++++++++++++-- tests/test_transform.py | 75 +++++++++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/subgrounds/transform.py b/subgrounds/transform.py index c152602..8e42f42 100644 --- a/subgrounds/transform.py +++ b/subgrounds/transform.py @@ -274,7 +274,13 @@ def transform_on_type(select: Selection) -> Selection: def transform_response(self, doc: Document, data: dict[str, Any]) -> dict[str, Any]: def transform(select: Selection, data: dict) -> None: match (select, data): - case (Selection(TypeMeta.FieldMeta(name=name), None, _, [] | None) | Selection(TypeMeta.FieldMeta(), name, _, [] | None), dict() as data) if name == self.fmeta.name and name not in data: + case ( + Selection(TypeMeta.FieldMeta(name=name), None, _, [] | None) | Selection(TypeMeta.FieldMeta(), name, _, [] | None), dict() as data + ) if name == self.fmeta.name and name not in data: + # Case where the selection selects a the syntheticfield of the curren transform + # that is not in the data blob and there are no inner selections + + # Try to grab the arguments to the synthetic field transform in the data blob arg_values = flatten(list(self.args | map(partial(select_data, data=data)))) try: @@ -282,17 +288,35 @@ def transform(select: Selection, data: dict) -> None: except ZeroDivisionError: data[name] = self.default + case ( + Selection(TypeMeta.FieldMeta(name=name), None, _, [] | None) | Selection(TypeMeta.FieldMeta(), name, _, [] | None), dict() as data + ) if name not in data: + # Case where the selection selects a regular field but it is not in the data blob (caused by None value at higher selection) + data[name] = None + case (Selection(TypeMeta.FieldMeta(name=name), None, _, [] | None) | Selection(TypeMeta.FieldMeta(), name, _, [] | None), dict() as data): + # Case where the selection selects a regular field and there are no inner selections + # (nothing to do) pass - case (Selection(TypeMeta.FieldMeta(name=name), None, _, inner_select) | Selection(TypeMeta.FieldMeta(), name, _, inner_select), dict() as data) if name in data: + + case ( + Selection(TypeMeta.FieldMeta(name=name), None, _, inner_select) | Selection(TypeMeta.FieldMeta(), name, _, inner_select), dict() as data + ) if name in data: match data[name]: case list() as elts: for elt in elts: for select in inner_select: transform(select, elt) + case dict() as elt: for select in inner_select: transform(select, elt) + + case None: + data[name] = {} + for select in inner_select: + transform(select, data[name]) + case _: raise Exception(f"transform_response: data for selection {select} is neither list or dict {data[name]}") diff --git a/tests/test_transform.py b/tests/test_transform.py index 7dbc743..309a55f 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1,11 +1,12 @@ import unittest -from typing import Any +from typing import Any, Callable import pytest from subgrounds.query import DataRequest, Document, Query, Selection from subgrounds.schema import TypeMeta, TypeRef -from subgrounds.subgraph import Subgraph +from subgrounds.subgraph import Subgraph, Object +from subgrounds.subgraph.fieldpath import FieldPath, SyntheticField from subgrounds.subgrounds import Subgrounds from subgrounds.transform import (DocumentTransform, LocalSyntheticField, TypeTransform) @@ -168,35 +169,73 @@ def test_localsyntheticfield_literal_roundtrip1( assert data == expected + +@pytest.mark.parametrize(["response", "expected", "synthfield_f", "object_f", "fpaths_f"], [ + ( + { + 'swaps': [{ + 'amount0In': '0.25', + 'amount0Out': '0.0', + 'amount1In': '0.0', + 'amount1Out': '89820.904371079570860909', + 'id': '0xf457e61e2aa310c8a7f01570bf96f24323fc317925c42f2a33d2061e1944df4d-0', + 'timestamp': '1638554699' + }] + }, + [{ + 'swaps': [{ + 'amount0In': 0.25, + 'amount0Out': 0.0, + 'amount1In': 0.0, + 'amount1Out': 89820.904371079570860909, + 'synthfield': 359283.61748431827, + 'id': '0xf457e61e2aa310c8a7f01570bf96f24323fc317925c42f2a33d2061e1944df4d-0', + 'timestamp': '1638554699' + }] + }], + lambda subgraph: abs(subgraph.Swap.amount1In - subgraph.Swap.amount1Out) / abs(subgraph.Swap.amount0In - subgraph.Swap.amount0Out), + lambda subgraph: subgraph.Swap, + lambda subgraph: [ + subgraph.Query.swaps.synthfield + ] + ), + ( + { + 'xa6436bbcebab2a86': None + }, + [{ + 'xa6436bbcebab2a86': { + "id": None, + "synthfield": "FOO" + } + }], + lambda _: SyntheticField.constant("FOO"), + lambda subgraph: subgraph.Pair, + lambda subgraph: [ + subgraph.Query.pair(id="ABC").id, + subgraph.Query.pair(id="ABC").synthfield, + ] + ) +]) def test_localsyntheticfield_toplevel_roundtrip( mocker, response: list[dict[str, Any]], + expected: list[dict[str, Any]], + synthfield_f: Callable[[Subgraph], SyntheticField], + object_f: Callable[[Subgraph], Object], + fpaths_f: Callable[[Subgraph], list[FieldPath]], subgraph: Subgraph, ): mocker.patch("subgrounds.client.query", return_value=response) - expected = [{ - 'swaps': [{ - 'amount0In': 0.25, - 'amount0Out': 0.0, - 'amount1In': 0.0, - 'amount1Out': 89820.904371079570860909, - 'price0': 359283.61748431827, - 'id': '0xf457e61e2aa310c8a7f01570bf96f24323fc317925c42f2a33d2061e1944df4d-0', - 'timestamp': '1638554699' - }] - }] - sg = Subgrounds( global_transforms=[], subgraphs={subgraph._url: subgraph} ) - subgraph.Swap.price0 = abs(subgraph.Swap.amount1In - subgraph.Swap.amount1Out) / abs(subgraph.Swap.amount0In - subgraph.Swap.amount0Out) + object_f(subgraph).synthfield = synthfield_f(subgraph) - req = sg.mk_request([ - subgraph.Query.swaps.price0 - ]) + req = sg.mk_request(fpaths_f(subgraph)) data = sg.execute(req) From f170076842198d3af73ff4c526bf871c2c2ec18e Mon Sep 17 00:00:00 2001 From: Christophe Vauclair Date: Tue, 1 Nov 2022 11:24:07 -0400 Subject: [PATCH 2/3] tests: fix broken tests --- tests/conftest.py | 20 ++++++++ tests/test_subgraph.py | 102 +++++++++++++++++------------------------ 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c4f4a6a..39f6f41 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,23 @@ def pairs_fieldmeta(): ) +@pytest.fixture +def pair_fieldmeta(): + return TypeMeta.FieldMeta( + name="pair", + description="", + args=[ + TypeMeta.ArgumentMeta( + name="id", + description="", + type=TypeRef.non_null("ID", kind="SCALAR"), + defaultValue=None, + ), + ], + type=TypeRef.Named(name="Pair", kind="OBJECT"), + ) + + @pytest.fixture def swaps_fieldmeta(): return TypeMeta.FieldMeta( @@ -87,6 +104,7 @@ def pair_objectmeta(): @pytest.fixture def schema( pairs_fieldmeta, + pair_fieldmeta, swaps_fieldmeta, swap_objectmeta, token_objectmeta, @@ -96,6 +114,7 @@ def schema( queryType={"name": 'Query'}, types=[], type_map={ + "ID": TypeMeta.ScalarMeta(name="ID", description=""), 'Int': TypeMeta.ScalarMeta(name='Int', description=''), 'Float': TypeMeta.ScalarMeta(name='Float', description=''), 'BigInt': TypeMeta.ScalarMeta(name='BigInt', description=''), @@ -108,6 +127,7 @@ def schema( 'Query': TypeMeta.ObjectMeta(name='Query', description='', fields=[ pairs_fieldmeta, swaps_fieldmeta, + pair_fieldmeta, ]), 'Swap': swap_objectmeta, 'Swap_filter': TypeMeta.InputObjectMeta(name='Swap_filter', description='', inputFields=[ diff --git a/tests/test_subgraph.py b/tests/test_subgraph.py index 5e0db28..6ae8ef1 100644 --- a/tests/test_subgraph.py +++ b/tests/test_subgraph.py @@ -21,6 +21,7 @@ def test_add_synthetic_field_1(subgraph: Subgraph): sfield = SyntheticField(identity, SyntheticField.FLOAT, subgraph.Pair.reserveUSD) expected = SchemaMeta(queryType={"name": 'Query'}, types=[], type_map={ + "ID": TypeMeta.ScalarMeta(name="ID", description=""), 'Int': TypeMeta.ScalarMeta(name='Int', description='', kind="SCALAR"), 'Float': TypeMeta.ScalarMeta(name='Float', description='', kind="SCALAR"), 'BigInt': TypeMeta.ScalarMeta(name='BigInt', description='', kind="SCALAR"), @@ -46,6 +47,15 @@ def test_add_synthetic_field_1(subgraph: Subgraph): TypeMeta.ArgumentMeta(name='orderDirection', description='', type=TypeRef.Named(name='OrderDirection', kind="ENUM"), defaultValue=None), ], type=TypeRef.non_null_list('Swap', kind="OBJECT") ), + TypeMeta.FieldMeta(name="pair",description="",args=[ + TypeMeta.ArgumentMeta( + name="id", + description="", + type=TypeRef.non_null("ID", kind="SCALAR"), + defaultValue=None, + ), + ], type=TypeRef.Named(name="Pair", kind="OBJECT"), + ) ]), 'Swap': TypeMeta.ObjectMeta(name='Swap', description='', fields=[ TypeMeta.FieldMeta(name='id', description='', args=[], type=TypeRef.Named(name='String', kind="SCALAR")), @@ -112,6 +122,7 @@ def test_add_synthetic_field_1(subgraph: Subgraph): def test_add_synthetic_field_2(subgraph: Subgraph): expected = SchemaMeta(queryType={"name": 'Query'}, types=[], type_map={ + "ID": TypeMeta.ScalarMeta(name="ID", description=""), 'Int': TypeMeta.ScalarMeta(name='Int', description='', kind="SCALAR"), 'Float': TypeMeta.ScalarMeta(name='Float', description='', kind="SCALAR"), 'BigInt': TypeMeta.ScalarMeta(name='BigInt', description='', kind="SCALAR"), @@ -135,8 +146,17 @@ def test_add_synthetic_field_2(subgraph: Subgraph): TypeMeta.ArgumentMeta(name='where', description='', type=TypeRef.Named(name='Swap_filter', kind="INPUT_OBJECT"), defaultValue=None), TypeMeta.ArgumentMeta(name='orderBy', description='', type=TypeRef.Named(name='Swap_orderBy', kind="ENUM"), defaultValue=None), TypeMeta.ArgumentMeta(name='orderDirection', description='', type=TypeRef.Named(name='OrderDirection', kind="ENUM"), defaultValue=None), - ], type=TypeRef.non_null_list('Swap', kind="OBJECT") + ], type=TypeRef.non_null_list('Swap', kind="OBJECT"), ), + TypeMeta.FieldMeta(name="pair",description="",args=[ + TypeMeta.ArgumentMeta( + name="id", + description="", + type=TypeRef.non_null("ID", kind="SCALAR"), + defaultValue=None, + ), + ], type=TypeRef.Named(name="Pair", kind="OBJECT"), + ) ]), 'Swap': TypeMeta.ObjectMeta(name='Swap', description='', fields=[ TypeMeta.FieldMeta(name='id', description='', args=[], type=TypeRef.Named(name='String', kind="SCALAR")), @@ -203,6 +223,7 @@ def test_add_synthetic_field_2(subgraph: Subgraph): def test_add_synthetic_field_3(subgraph: Subgraph): expected = SchemaMeta(queryType={"name": 'Query'}, types=[], type_map={ + "ID": TypeMeta.ScalarMeta(name="ID", description=""), 'Int': TypeMeta.ScalarMeta(name='Int', description='', kind="SCALAR"), 'Float': TypeMeta.ScalarMeta(name='Float', description='', kind="SCALAR"), 'BigInt': TypeMeta.ScalarMeta(name='BigInt', description='', kind="SCALAR"), @@ -228,6 +249,15 @@ def test_add_synthetic_field_3(subgraph: Subgraph): TypeMeta.ArgumentMeta(name='orderDirection', description='', type=TypeRef.Named(name='OrderDirection', kind="ENUM"), defaultValue=None), ], type=TypeRef.non_null_list('Swap', kind="OBJECT") ), + TypeMeta.FieldMeta(name="pair",description="",args=[ + TypeMeta.ArgumentMeta( + name="id", + description="", + type=TypeRef.non_null("ID", kind="SCALAR"), + defaultValue=None, + ), + ], type=TypeRef.Named(name="Pair", kind="OBJECT"), + ) ]), 'Swap': TypeMeta.ObjectMeta(name='Swap', description='', fields=[ TypeMeta.FieldMeta(name='id', description='', args=[], type=TypeRef.Named(name='String', kind="SCALAR")), @@ -296,6 +326,7 @@ def test_add_synthetic_field_3(subgraph: Subgraph): def test_add_synthetic_field_4(subgraph: Subgraph): expected = SchemaMeta(queryType={"name": 'Query'}, types=[], type_map={ + "ID": TypeMeta.ScalarMeta(name="ID", description=""), 'Int': TypeMeta.ScalarMeta(name='Int', description='', kind="SCALAR"), 'Float': TypeMeta.ScalarMeta(name='Float', description='', kind="SCALAR"), 'BigInt': TypeMeta.ScalarMeta(name='BigInt', description='', kind="SCALAR"), @@ -321,6 +352,15 @@ def test_add_synthetic_field_4(subgraph: Subgraph): TypeMeta.ArgumentMeta(name='orderDirection', description='', type=TypeRef.Named(name='OrderDirection', kind="ENUM"), defaultValue=None), ], type=TypeRef.non_null_list('Swap', kind="OBJECT") ), + TypeMeta.FieldMeta(name="pair",description="",args=[ + TypeMeta.ArgumentMeta( + name="id", + description="", + type=TypeRef.non_null("ID", kind="SCALAR"), + defaultValue=None, + ), + ], type=TypeRef.Named(name="Pair", kind="OBJECT"), + ) ]), 'Swap': TypeMeta.ObjectMeta(name='Swap', description='', fields=[ TypeMeta.FieldMeta(name='id', description='', args=[], type=TypeRef.Named(name='String', kind="SCALAR")), @@ -408,14 +448,6 @@ def test_object(subgraph: Subgraph): def test_field_path_1(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta(name='Pair', description='', fields=[ - # TypeMeta.FieldMeta(name='id', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta(name='token0', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta(name='token1', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta(name='reserveUSD', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta(name='priceToken0', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta(name='priceToken1', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # ]), TypeRef.Named(name="Pair", kind="OBJECT"), TypeRef.Named(name="Token", kind="OBJECT"), [ @@ -432,14 +464,6 @@ def test_field_path_1(subgraph: Subgraph): def test_field_path_2(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta('Pair', '', fields=[ - # TypeMeta.FieldMeta('id', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta('token0', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta('token1', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta('reserveUSD', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta('priceToken0', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta('priceToken1', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # ]), TypeRef.Named(name="Pair", kind="OBJECT"), TypeRef.Named(name="String", kind="SCALAR"), [ @@ -456,14 +480,6 @@ def test_field_path_2(subgraph: Subgraph): def test_field_path_3(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta('Pair', '', fields=[ - # TypeMeta.FieldMeta('id', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta('token0', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta('token1', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta('reserveUSD', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta('priceToken0', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta('priceToken1', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # ]), TypeRef.Named(name="Pair", kind="OBJECT"), TypeRef.Named(name="String", kind="SCALAR"), [ @@ -483,14 +499,6 @@ def test_synthetic_field_path_1(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta(name='Pair', description='', fields=[ - # TypeMeta.FieldMeta(name='id', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta(name='token0', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta(name='token1', description="", args=[], type=TypeRef.Named(name="Token", kind="OBJECT")), - # TypeMeta.FieldMeta(name='reserveUSD', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta(name='priceToken0', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # TypeMeta.FieldMeta(name='priceToken1', description="", args=[], type=TypeRef.Named(name="BigDecimal", kind="SCALAR")), - # ]), TypeRef.Named(name="Pair", kind="OBJECT"), TypeRef.Named(name="Float", kind="SCALAR"), [ @@ -510,12 +518,6 @@ def test_synthetic_field_path_2(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta('Token', '', fields=[ - # TypeMeta.FieldMeta('id', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta('name', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta('symbol', description="", args=[], type=TypeRef.Named(name="String", kind="SCALAR")), - # TypeMeta.FieldMeta('decimals', description="", args=[], type=TypeRef.Named(name="Int", kind="SCALAR")), - # ]), TypeRef.Named(name="Token", kind="OBJECT"), TypeRef.Named(name="String", kind="SCALAR"), [ @@ -639,15 +641,6 @@ def test_field_path_args_2(subgraph: Subgraph): def test_field_path_args_3(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta(name='Query', description='', fields=[ - # TypeMeta.FieldMeta(name='pairs', description='', args=[ - # TypeMeta.ArgumentMeta(name='first', description='', type=TypeRef.Named(name="Int", kind="SCALAR"), defaultValue=None), - # TypeMeta.ArgumentMeta(name='where', description='', type=TypeRef.Named(name="Pair_filter", kind="INPUT_OBJECT"), defaultValue=None), - # TypeMeta.ArgumentMeta(name='orderBy', description='', type=TypeRef.Named(name="Pair_orderBy", kind="ENUM"), defaultValue=None), - # TypeMeta.ArgumentMeta(name='orderDirection', description='', type=TypeRef.Named(name="OrderDirection", kind="ENUM"), defaultValue=None), - # ], type=TypeRef.non_null_list("Pair", kind="OBJECT")), - # TypeMeta.FieldMeta('swaps', description="", args=[], type=TypeRef.non_null_list("Swap", kind="OBJECT")), - # ]), TypeRef.Named(name='Query', kind="OBJECT"), TypeRef.non_null_list('Pair', kind="OBJECT"), [ @@ -656,10 +649,6 @@ def test_field_path_args_3(subgraph: Subgraph): 'first': 100, 'orderBy': 'reserveUSD' }, - # [ - # Argument('first', InputValue.Int(100)), - # Argument('orderBy', InputValue.Enum('reserveUSD')) - # ], TypeMeta.FieldMeta(name='pairs', description='', args=[ TypeMeta.ArgumentMeta(name='first', description='', type=TypeRef.Named(name="Int", kind="SCALAR"), defaultValue=None), TypeMeta.ArgumentMeta(name='skip', description='', type=TypeRef.Named(name="Int", kind="SCALAR"), defaultValue=None), @@ -683,15 +672,6 @@ def test_field_path_args_3(subgraph: Subgraph): def test_field_path_extend_1(subgraph: Subgraph): expected = FieldPath( subgraph, - # TypeMeta.ObjectMeta('Query', '', fields=[ - # TypeMeta.FieldMeta('pairs', '', [ - # TypeMeta.ArgumentMeta('first', '', TypeRef.Named(name="Int", kind="SCALAR"), None), - # TypeMeta.ArgumentMeta('where', '', TypeRef.Named(name="Pair_filter", kind="INPUT_OBJECT"), None), - # TypeMeta.ArgumentMeta('orderBy', '', TypeRef.Named(name="Pair_orderBy", kind="ENUM"), None), - # TypeMeta.ArgumentMeta('orderDirection', '', TypeRef.Named(name="OrderDirection", kind="ENUM"), None), - # ], TypeRef.non_null_list("Pair", kind="OBJECT")), - # TypeMeta.FieldMeta('swaps', description="", args=[], type=TypeRef.non_null_list("Swap", kind="OBJECT")), - # ]), TypeRef.Named(name="Query", kind="OBJECT"), TypeRef.Named(name="String", kind="SCALAR"), [ From 836912315ce8f8d79d23bb7da1a92ad5e8371af8 Mon Sep 17 00:00:00 2001 From: Christophe Vauclair Date: Tue, 1 Nov 2022 11:27:47 -0400 Subject: [PATCH 3/3] fix: bug where some SyntheticField arguments would be None --- subgrounds/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subgrounds/transform.py b/subgrounds/transform.py index 8e42f42..4a96172 100644 --- a/subgrounds/transform.py +++ b/subgrounds/transform.py @@ -285,7 +285,7 @@ def transform(select: Selection, data: dict) -> None: try: data[name] = self.f(*arg_values) - except ZeroDivisionError: + except Exception: data[name] = self.default case (