From e92ba07ff555ebbc565edb489504641982efd898 Mon Sep 17 00:00:00 2001 From: Lucas Pedroza Date: Thu, 12 Oct 2023 12:48:29 +0200 Subject: [PATCH 1/2] Add support for array and object encoding --- fauna/encoding/encoder.py | 93 ++--- tests/integration/test_composition.py | 14 + tests/unit/conftest.py | 86 +++++ tests/unit/test_encoding.py | 517 +++++++++++++------------- 4 files changed, 403 insertions(+), 307 deletions(-) diff --git a/fauna/encoding/encoder.py b/fauna/encoding/encoder.py index 5953c993..b3c8946e 100644 --- a/fauna/encoding/encoder.py +++ b/fauna/encoding/encoder.py @@ -24,29 +24,29 @@ class FaunaEncoder: +-------------------------------+---------------+ | Python | Fauna Tags | +===============================+===============+ - | dict | @object | + | dict | object | +-------------------------------+---------------+ | list, tuple | array | +-------------------------------+---------------+ - | str | string | + | str | value, N/A | +-------------------------------+---------------+ - | int 32-bit signed | @int | + | int 32-bit signed | value, @int | +-------------------------------+---------------+ - | int 64-bit signed | @long | + | int 64-bit signed | value, @long | +-------------------------------+---------------+ - | float | @double | + | float | value, @double| +-------------------------------+---------------+ - | datetime.datetime | @time | + | datetime.datetime | value, @time | +-------------------------------+---------------+ - | datetime.date | @date | + | datetime.date | value, @date | +-------------------------------+---------------+ - | True | True | + | True | value, N/A | +-------------------------------+---------------+ - | False | False | + | False | value, N/A | +-------------------------------+---------------+ - | None | None | + | None | value, N/A | +-------------------------------+---------------+ - | *Document | @ref | + | *Document | value, @ref | +-------------------------------+---------------+ | *DocumentReference | @ref | +-------------------------------+---------------+ @@ -54,28 +54,22 @@ class FaunaEncoder: +-------------------------------+---------------+ | Query | fql | +-------------------------------+---------------+ - | ValueFragment | value | - +-------------------------------+---------------+ - | TemplateFragment | string | - +-------------------------------+---------------+ """ @staticmethod def encode(obj: Any) -> Any: - """Encodes supported objects into the tagged format. + """Encodes supported objects into the wire protocol. Examples: - - Up to 32-bit ints encode to { "@int": "..." } - - Up to 64-bit ints encode to { "@long": "..." } - - Floats encode to { "@double": "..." } - - datetime encodes to { "@time": "..." } - - date encodes to { "@date": "..." } - - DocumentReference encodes to { "@doc": "..." } - - Module encodes to { "@mod": "..." } + - Up to 32-bit ints encode to {"value": { "@int": "..." }} + - Up to 64-bit ints encode to {"value": { "@long": "..." }} + - Floats encode to {"value": { "@double": "..." }} + - datetime encodes to {"value": { "@time": "..." }} + - date encodes to {"value": { "@date": "..." }} + - Objects encode to {"object": { ... }}, and its values are recursively encoded + - Lists and Tuples encode to {"array": [...]}, and its values are recursively encoded - Query encodes to { "fql": [...] } - - ValueFragment encodes to { "value": } - - LiteralFragment encodes to a string :raises ValueError: If value cannot be encoded, cannot be encoded safely, or there's a circular reference. :param obj: the object to decode @@ -126,10 +120,6 @@ def from_named_doc_ref(obj: NamedDocumentReference): def from_mod(obj: Module): return {"@mod": obj.name} - @staticmethod - def from_dict(obj: Any): - return {"@object": obj} - @staticmethod def from_none(): return None @@ -139,11 +129,7 @@ def from_fragment(obj: Fragment): if isinstance(obj, LiteralFragment): return obj.get() elif isinstance(obj, ValueFragment): - v = obj.get() - if isinstance(v, Query): - return FaunaEncoder.from_query_interpolation_builder(v) - else: - return {"value": FaunaEncoder.encode(v)} + return FaunaEncoder.encode(obj.get()) else: raise ValueError(f"Unknown fragment type: {type(obj)}") @@ -157,32 +143,32 @@ def _encode(o: Any, _markers: Optional[Set] = None): _markers = set() if isinstance(o, str): - return FaunaEncoder.from_str(o) + return {"value": FaunaEncoder.from_str(o)} elif o is None: - return FaunaEncoder.from_none() + return {"value": FaunaEncoder.from_none()} elif o is True: - return FaunaEncoder.from_bool(o) + return {"value": FaunaEncoder.from_bool(o)} elif o is False: - return FaunaEncoder.from_bool(o) + return {"value": FaunaEncoder.from_bool(o)} elif isinstance(o, int): - return FaunaEncoder.from_int(o) + return {"value": FaunaEncoder.from_int(o)} elif isinstance(o, float): - return FaunaEncoder.from_float(o) + return {"value": FaunaEncoder.from_float(o)} elif isinstance(o, Module): - return FaunaEncoder.from_mod(o) + return {"value": FaunaEncoder.from_mod(o)} elif isinstance(o, DocumentReference): - return FaunaEncoder.from_doc_ref(o) + return {"value": FaunaEncoder.from_doc_ref(o)} elif isinstance(o, NamedDocumentReference): - return FaunaEncoder.from_named_doc_ref(o) + return {"value": FaunaEncoder.from_named_doc_ref(o)} elif isinstance(o, datetime): - return FaunaEncoder.from_datetime(o) + return {"value": FaunaEncoder.from_datetime(o)} elif isinstance(o, date): - return FaunaEncoder.from_date(o) + return {"value": FaunaEncoder.from_date(o)} elif isinstance(o, Document): - return FaunaEncoder.from_doc_ref(DocumentReference(o.coll, o.id)) + return {"value": FaunaEncoder.from_doc_ref(DocumentReference(o.coll, o.id))} elif isinstance(o, NamedDocument): - return FaunaEncoder.from_named_doc_ref( - NamedDocumentReference(o.coll, o.name)) + return {"value": FaunaEncoder.from_named_doc_ref( + NamedDocumentReference(o.coll, o.name))} elif isinstance(o, NullDocument): return FaunaEncoder.encode(o.ref) elif isinstance(o, (list, tuple)): @@ -201,7 +187,7 @@ def _encode_list(lst, markers): raise ValueError("Circular reference detected") markers.add(id(lst)) - return [FaunaEncoder._encode(elem, markers) for elem in lst] + return {"array": [FaunaEncoder._encode(elem, markers) for elem in lst]} @staticmethod def _encode_dict(dct, markers): @@ -210,11 +196,4 @@ def _encode_dict(dct, markers): raise ValueError("Circular reference detected") markers.add(id(dct)) - if any(i in _RESERVED_TAGS for i in dct.keys()): - return { - "@object": { - k: FaunaEncoder._encode(v, markers) for k, v in dct.items() - } - } - else: - return {k: FaunaEncoder._encode(v, markers) for k, v in dct.items()} + return {"object": {k: FaunaEncoder._encode(v, markers) for k, v in dct.items()}} diff --git a/tests/integration/test_composition.py b/tests/integration/test_composition.py index 45fcfee0..f8e70408 100644 --- a/tests/integration/test_composition.py +++ b/tests/integration/test_composition.py @@ -45,3 +45,17 @@ def update_doc_by_email(email: str, data: dict): assert result.data.id == doc.id assert result.data.coll == doc.coll assert result.data.ts != doc.ts + + +def test_array_composition(client): + queries = [fql("1"), fql("2"), {"key": 3}, [fql("${inner}", inner={"inner": "thing"})]] + q = fql("${queries}", queries=queries) + res = client.query(q).data + assert [1, 2, {'key': 3}, [{'inner': 'thing'}]] == res + + +def test_object_composition(client): + queries = {1: fql("1"), 2: fql("2"), 3: {"key": fql("3")}, 4: {"inner": fql("${inner}", inner=["inner", "thing"])}} + q = fql("${queries}", queries=queries) + res = client.query(q).data + assert {'1': 1, '2': 2, '3': {'key': 3}, '4': {'inner': ['inner', 'thing']}} == res diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 0ed27c06..3c9e28d3 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -146,3 +146,89 @@ def complex_typed_object(): } }] } + +@pytest.fixture +def complex_wire_encoded_object(): + return { + 'object': { + 'bugs_coll': { + 'value': {'@mod': 'Bugs'} + }, + 'bug': { + 'value': { + '@ref': { + 'id': "123", + 'coll': { + '@mod': 'Bugs' + } + } + } + }, + 'name': + {'value': 'fir'}, + 'age': { + 'value': {'@int': '200'} + }, + 'birthdate': { + 'value': {'@date': '1823-02-08'} + }, + 'molecules': { + 'value': {'@long': '999999999999999999'} + }, + 'circumference': { + 'value': {'@double': '3.82'} + }, + 'created_at': { + 'value': {'@time': '2003-02-08T13:28:12.000555+00:00'} + }, + 'extras': { + 'object': { + 'nest': { + 'object': { + '@object': { + 'object': { + 'egg': { + 'object': { + 'fertilized': {'value': False} + } + } + } + }, + 'num_sticks': { + 'value': {'@int': '58'} + }, + } + } + } + + }, + 'measurements': { + 'array': [{ + 'object': { + 'id': { + 'value': {'@int': '1'} + }, + 'employee': { + 'value': {'@int': '3'} + }, + 'time': { + 'value': {'@time': '2013-02-08T12:00:05.000123+00:00'} + } + } + }, { + 'object': { + 'id': { + 'value': {'@int': '2'} + }, + 'employee': { + 'value': {'@int': '5'} + }, + 'time': { + 'value': {'@time': '2023-02-08T14:22:01.000001+00:00'} + } + } + }] + } + + } + } diff --git a/tests/unit/test_encoding.py b/tests/unit/test_encoding.py index a32bb7db..9278e8bb 100644 --- a/tests/unit/test_encoding.py +++ b/tests/unit/test_encoding.py @@ -12,75 +12,75 @@ fixed_datetime = datetime.fromisoformat("2023-03-17T00:00:00+00:00") -def test_encode_decode_primitives(subtests): +def test_encode_primitives(subtests): with subtests.test(msg="encode string"): test = "hello" encoded = FaunaEncoder.encode(test) - assert test == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": test} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode true"): test = True encoded = FaunaEncoder.encode(test) - assert test == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": test} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode false"): test = False encoded = FaunaEncoder.encode(test) - assert test == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": test} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode int into @int"): test = 10 encoded = FaunaEncoder.encode(test) - assert {"@int": "10"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@int": "10"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode max 32-bit signed int into @int"): test = 2147483647 encoded = FaunaEncoder.encode(test) - assert {"@int": "2147483647"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@int": "2147483647"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode min 32-bit signed int into @int"): test = -2147483648 encoded = FaunaEncoder.encode(test) - assert {"@int": "-2147483648"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@int": "-2147483648"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode max 32-bit signed int + 1 into @long"): test = 2147483648 encoded = FaunaEncoder.encode(test) - assert {"@long": "2147483648"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@long": "2147483648"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode min 32-bit signed int - 1 into @long"): test = -2147483649 encoded = FaunaEncoder.encode(test) - assert {"@long": "-2147483649"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@long": "-2147483649"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode max 64-bit signed int into @long"): test = 9223372036854775807 encoded = FaunaEncoder.encode(test) - assert {"@long": "9223372036854775807"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@long": "9223372036854775807"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode min 64-bit signed int into @long"): test = -9223372036854775808 encoded = FaunaEncoder.encode(test) - assert {"@long": "-9223372036854775808"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@long": "-9223372036854775808"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode max 64-bit signed int + 1 throws error"): @@ -98,22 +98,22 @@ def test_encode_decode_primitives(subtests): with subtests.test(msg="encode negative float into @double"): test = -100.0 encoded = FaunaEncoder.encode(test) - assert {"@double": "-100.0"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@double": "-100.0"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode positive float into @double"): test = 9.999999999999 encoded = FaunaEncoder.encode(test) - assert {"@double": "9.999999999999"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@double": "9.999999999999"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode None into None"): test = {"foo": None} encoded = FaunaEncoder.encode(test) - assert test == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"object": {"foo": {"value": None}}} == encoded + decoded = FaunaDecoder.decode({"foo": None}) assert test == decoded @@ -121,16 +121,16 @@ def test_encode_dates_times(subtests): with subtests.test(msg="encode date into @date"): test = date(2023, 2, 28) encoded = FaunaEncoder.encode(test) - assert {"@date": "2023-02-28"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@date": "2023-02-28"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="encode datetime into @time"): test = datetime( 2023, 2, 28, 10, 10, 10, 1, tzinfo=timezone(timedelta(0), '+00:00')) encoded = FaunaEncoder.encode(test) - assert {"@time": "2023-02-28T10:10:10.000001+00:00"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@time": "2023-02-28T10:10:10.000001+00:00"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded with subtests.test(msg="datetimes without tzinfo raise ValueError"): @@ -143,8 +143,8 @@ def test_encode_document_references(subtests): doc_ref = DocumentReference.from_string("Col:123") with subtests.test(msg="encode/decode with @doc"): encoded = FaunaEncoder.encode(doc_ref) - assert {'@ref': {'coll': {'@mod': 'Col'}, 'id': "123"}} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {'value': {'@ref': {'coll': {'@mod': 'Col'}, 'id': "123"}}} == encoded + decoded = FaunaDecoder.decode(encoded['value']) assert doc_ref == decoded with subtests.test(msg="decode doc ref from @ref"): @@ -156,9 +156,9 @@ def test_encode_document_references(subtests): def test_null_docments(subtests): with subtests.test(msg="encode null doc"): null_doc = NullDocument(DocumentReference("NDCol", "456"), "not found") - test = {"@ref": {"id": "456", "coll": {"@mod": "NDCol"}}} + expected = {"value": {"@ref": {"id": "456", "coll": {"@mod": "NDCol"}}}} encoded = FaunaEncoder.encode(null_doc) - assert encoded == test + assert expected == encoded with subtests.test(msg="decode null doc"): null_doc = NullDocument(DocumentReference("NDCol", "456"), "not found") @@ -178,9 +178,9 @@ def test_null_docments(subtests): with subtests.test(msg="encode named null doc"): null_doc = NullDocument( NamedDocumentReference("Collection", "Party"), "not found") - test = {"@ref": {"name": "Party", "coll": {"@mod": "Collection"}}} + expected = {"value": {"@ref": {"name": "Party", "coll": {"@mod": "Collection"}}}} encoded = FaunaEncoder.encode(null_doc) - assert encoded == test + assert expected == encoded with subtests.test(msg="decode named null doc"): null_doc = NullDocument( @@ -203,8 +203,8 @@ def test_encode_named_document_references(subtests): doc_ref = NamedDocumentReference("Col", "Hi") with subtests.test(msg="encode/decode with @doc"): encoded = FaunaEncoder.encode(doc_ref) - assert {"@ref": {"name": "Hi", "coll": {"@mod": "Col"}}} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@ref": {"name": "Hi", "coll": {"@mod": "Col"}}}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert doc_ref == decoded with subtests.test(msg="decode doc ref from @ref"): @@ -219,13 +219,13 @@ def test_encode_documents(subtests): id="123", coll="Dogs", ts=fixed_datetime, data={"name": "Scout"}) encoded = FaunaEncoder.encode(test) # should encode to a ref! - assert {"@ref": {"id": "123", "coll": {"@mod": "Dogs"}}} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@ref": {"id": "123", "coll": {"@mod": "Dogs"}}}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) # refs will decode into references, not Documents assert DocumentReference("Dogs", "123") == decoded with subtests.test(msg="decode document with id and name"): - encoded = { + test = { "@doc": { "id": "123", "coll": { @@ -237,7 +237,7 @@ def test_encode_documents(subtests): "name": "Scout" } } - decoded = FaunaDecoder.decode(encoded) + decoded = FaunaDecoder.decode(test) assert Document( id="123", coll="Dogs", ts=fixed_datetime, data={"name": "Scout"}) == decoded @@ -248,13 +248,13 @@ def test_encode_named_documents(subtests): test = NamedDocument(name="DogSchema", coll="Dogs", ts=fixed_datetime) encoded = FaunaEncoder.encode(test) # should encode to a ref! - assert {"@ref": {"name": "DogSchema", "coll": {"@mod": "Dogs"}}} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@ref": {"name": "DogSchema", "coll": {"@mod": "Dogs"}}}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) # refs will decode into references, not Documents assert NamedDocumentReference("Dogs", "DogSchema") == decoded with subtests.test(msg="decode named document"): - encoded = { + test = { "@doc": { "coll": { "@mod": "Dogs" @@ -266,7 +266,7 @@ def test_encode_named_documents(subtests): "other": "data", } } - decoded = FaunaDecoder.decode(encoded) + decoded = FaunaDecoder.decode(test) assert NamedDocument( name="Scout", coll="Dogs", ts=fixed_datetime, data={"other": "data"}) == decoded @@ -276,12 +276,12 @@ def test_encode_modules(subtests): with subtests.test(msg="encode module into @mod"): test = Module("Math") encoded = FaunaEncoder.encode(test) - assert {"@mod": "Math"} == encoded - decoded = FaunaDecoder.decode(encoded) + assert {"value": {"@mod": "Math"}} == encoded + decoded = FaunaDecoder.decode(encoded["value"]) assert test == decoded -def test_encode_sets(subtests): +def test_decode_sets(subtests): with subtests.test(msg="decode @set into page"): test = {"@set": {"data": [1, 2], "after": "asdflkj"}} decoded = FaunaDecoder.decode(test) @@ -304,81 +304,6 @@ def test_encode_sets(subtests): FaunaEncoder.encode(p) -def test_encode_collections(subtests): - test_dict = { - "int": - 10, - "double": - 10.0, - "long": - 2147483649, - "string": - "foo", - "true": - True, - "false": - False, - "none": - None, - "date": - date(2023, 2, 28), - "time": - datetime( - 2023, - 2, - 28, - 10, - 10, - 10, - 10, - tzinfo=timezone(timedelta(0), '+00:00')), - } - - encoded_dict = { - "int": { - "@int": "10" - }, - "double": { - "@double": "10.0" - }, - "long": { - "@long": "2147483649" - }, - "string": "foo", - "true": True, - "false": False, - "none": None, - "date": { - "@date": "2023-02-28" - }, - "time": { - "@time": "2023-02-28T10:10:10.000010+00:00" - }, - } - - with subtests.test(msg="encode dict into dict"): - encoded = FaunaEncoder.encode(test_dict) - assert encoded_dict == encoded - decoded = FaunaDecoder.decode(encoded) - assert test_dict == decoded - - with subtests.test(msg="encode list into list"): - test = list(test_dict.values()) - expected = list(encoded_dict.values()) - encoded = FaunaEncoder.encode(test) - assert expected == encoded - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - - with subtests.test(msg="encode tuple into list"): - test = tuple(test_dict.values()) - expected = list(encoded_dict.values()) - encoded = FaunaEncoder.encode(test) - assert expected == encoded - decoded = FaunaDecoder.decode(encoded) - assert list(test_dict.values()) == decoded - - def test_encode_with_circular_references(subtests): with subtests.test(msg="circular reference with dict"): @@ -398,80 +323,109 @@ def test_encode_with_circular_references(subtests): def test_encode_int_conflicts(subtests): - with subtests.test(msg="@int conflict with int type"): + with subtests.test(msg="encode @int conflict with int type"): test = {"@int": 10} - expected = {"@object": {"@int": {"@int": "10"}}} + expected = {"object": {"@int": {"value": {"@int": "10"}}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - with subtests.test(msg="@int conflict with other type"): + with subtests.test(msg="encode @int conflict with other type"): test = {"@int": "bar"} - expected = {"@object": {"@int": "bar"}} + expected = {"object": {"@int": {"value": "bar"}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + + with subtests.test(msg="decode @int conflict with int type"): + test = {"@object": {"@int": "10"}} + decoded = FaunaDecoder.decode(test) + assert {"@int": "10"} == decoded + + with subtests.test(msg="decode @int conflict with other type"): + test = {"@object": {"@int": "bar"}} + decoded = FaunaDecoder.decode(test) + assert {"@int": "bar"} == decoded def test_encode_long_conflicts(subtests): - with subtests.test(msg="@long conflict with long type"): + with subtests.test(msg="encode @long conflict with long type"): test = {"@long": 2147483649} - expected = {"@object": {"@long": {"@long": "2147483649"}}} + expected = {"object": {"@long": {"value": {"@long": "2147483649"}}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - with subtests.test(msg="@long conflict with other type"): + with subtests.test(msg="encode @long conflict with other type"): test = {"@long": "bar"} - expected = {"@object": {"@long": "bar"}} + expected = {"object": {"@long": {"value": "bar"}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + + + with subtests.test(msg="decode @long conflict with long type"): + expected = {"@long": 2147483649} + test = {"@object": {"@long": {"@long": "2147483649"}}} + decoded = FaunaDecoder.decode(test) + assert expected == decoded + + with subtests.test(msg="decode @long conflict with other type"): + expected = {"@long": "bar"} + test = {"@object": {"@long": "bar"}} + decoded = FaunaDecoder.decode(test) + assert expected == decoded def test_encode_float_conflicts(subtests): - with subtests.test(msg="@double conflict with float type"): + with subtests.test(msg="encode @double conflict with double type"): test = {"@double": 10.2} - expected = {"@object": {"@double": {"@double": "10.2"}}} + expected = {"object": {"@double": {"value": {"@double": "10.2"}}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - with subtests.test(msg="@double conflict with other type"): + with subtests.test(msg="encode @double conflict with other type"): test = {"@double": "bar"} - expected = {"@object": {"@double": "bar"}} + expected = {"object": {"@double": {"value": "bar"}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + + with subtests.test(msg="decode @double conflict with double type"): + expected = {"@double": 10.2} + test = {"@object": {"@double": {"@double": "10.2"}}} + decoded = FaunaDecoder.decode(test) + assert expected == decoded + + with subtests.test(msg="decode @double conflict with other type"): + expected = {"@double": "bar"} + test = {"@object": {"@double": "bar"}} + decoded = FaunaDecoder.decode(test) + assert expected == decoded def test_encode_date_time_conflicts(subtests): - with subtests.test(msg="@date conflict with date type"): + with subtests.test(msg="encode @date conflict with date type"): test = {"@date": date(2023, 2, 28)} - expected = {"@object": {"@date": {"@date": "2023-02-28"}}} + expected = {"object": {"@date": {"value": {"@date": "2023-02-28"}}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - with subtests.test(msg="@date conflict with other type"): + with subtests.test(msg="encode @date conflict with other type"): test = {"@date": "bar"} - expected = {"@object": {"@date": "bar"}} + expected = {"object": {"@date": {"value": "bar"}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - with subtests.test(msg="@time conflict with date type"): + with subtests.test(msg="decode @date conflict with date type"): + test = {"@object": {"@date": {"@date": "2023-02-28"}}} + decoded = FaunaDecoder.decode(test) + assert {"@date": date(2023, 2, 28)} == decoded + + with subtests.test(msg="decode @date conflict with other type"): + test = {"@object": {"@date": "bar"}} + decoded = FaunaDecoder.decode(test) + assert {"@date": "bar"} == decoded + + with subtests.test(msg="encode @time conflict with date type"): test = { "@time": datetime( @@ -485,31 +439,56 @@ def test_encode_date_time_conflicts(subtests): tzinfo=timezone(timedelta(0), '+00:00')) } expected = { - "@object": { + "object": { "@time": { - "@time": "2023-02-28T10:10:10.000010+00:00" + "value": {"@time": "2023-02-28T10:10:10.000010+00:00"} } } } + encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - with subtests.test(msg="@time conflict with other type"): + with subtests.test(msg="encode @time conflict with other type"): test = {"@time": "bar"} - expected = {"@object": {"@time": "bar"}} + expected = {"object": {"@time": {"value":"bar"}}} encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + with subtests.test(msg="decode @time conflict with date type"): + expected = { + "@time": + datetime( + 2023, + 2, + 28, + 10, + 10, + 10, + 10, + tzinfo=timezone(timedelta(0), '+00:00')) + } + test = { + "@object": { + "@time": { + "@time": "2023-02-28T10:10:10.000010+00:00" + } + } + } + decoded = FaunaDecoder.decode(test) + assert expected == decoded + + with subtests.test(msg="@time conflict with other type"): + test = {"@object": {"@time": "bar"}} + decoded = FaunaDecoder.decode(test) + assert {"@time": "bar"} == decoded -def test_encode_fauna_type_conflicts(subtests): - with subtests.test(msg="@ref conflict with ref type"): +def test_decode_fauna_type_conflicts(subtests): + + with subtests.test(msg="decode @ref conflict with ref type"): test = {"@ref": DocumentReference.from_string("Col:123")} - expected = { + typed = { "@object": { "@ref": { "@ref": { @@ -521,60 +500,46 @@ def test_encode_fauna_type_conflicts(subtests): } } } - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + decoded = FaunaDecoder.decode(typed) assert test == decoded with subtests.test(msg="@doc conflict with other type"): test = {"@doc": "bar"} - expected = {"@object": {"@doc": "bar"}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@doc": "bar"}} + decoded = FaunaDecoder.decode(typed) assert test == decoded with subtests.test(msg="@mod conflict with mod type"): test = {"@mod": Module("Math")} - expected = {"@object": {"@mod": {"@mod": "Math"}}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@mod": {"@mod": "Math"}}} + decoded = FaunaDecoder.decode(typed) assert test == decoded with subtests.test(msg="@mod conflict with other type"): test = {"@mod": "bar"} - expected = {"@object": {"@mod": "bar"}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@mod": "bar"}} + decoded = FaunaDecoder.decode(typed) assert test == decoded -def test_encode_object_conflicts(subtests): +def test_decode_object_conflicts(subtests): with subtests.test(msg="@object conflicts with type"): test = {"@object": 10} - expected = {"@object": {"@object": {"@int": "10"}}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@object": {"@int": "10"}}} + decoded = FaunaDecoder.decode(typed) assert test == decoded with subtests.test(msg="@object conflicts with @int"): test = {"@object": {"@int": "bar"}} - expected = {"@object": {"@object": {"@object": {"@int": "bar"}}}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@object": {"@object": {"@int": "bar"}}}} + decoded = FaunaDecoder.decode(typed) assert test == decoded with subtests.test(msg="@object conflicts with @object"): test = {"@object": {"@object": "bar"}} - expected = {"@object": {"@object": {"@object": {"@object": "bar"}}}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@object": {"@object": {"@object": "bar"}}}} + decoded = FaunaDecoder.decode(typed) assert test == decoded @@ -582,26 +547,46 @@ def test_encode_multiple_keys_in_conflict(subtests): with subtests.test(msg="conflict with other non-conflicting keys"): test = {"@int": "foo", "tree": "birch"} - expected = {"@object": {"@int": "foo", "tree": "birch"}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@int": "foo", "tree": "birch"}} + decoded = FaunaDecoder.decode(typed) assert test == decoded with subtests.test(msg="conflict with other conflicting keys"): test = {"@int": "foo", "@double": "birch"} - expected = {"@object": {"@int": "foo", "@double": "birch"}} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) + typed = {"@object": {"@int": "foo", "@double": "birch"}} + decoded = FaunaDecoder.decode(typed) assert test == decoded def test_encode_nested_conflict(subtests): - with subtests.test(msg="nested conflicts"): + with subtests.test(msg="encode nested conflicts"): test = {"@int": {"@date": {"@time": {"@long": 10}}}} expected = { + "object": { + "@int": { + "object": { + "@date": { + "object": { + "@time": { + "object": { + "@long": { + "value": {"@int": "10"} + } + } + } + } + } + } + } + } + } + encoded = FaunaEncoder.encode(test) + assert encoded == expected + + with subtests.test(msg="decode nested conflicts"): + expected = {"@int": {"@date": {"@time": {"@long": 10}}}} + test = { "@object": { "@int": { "@object": { @@ -620,62 +605,94 @@ def test_encode_nested_conflict(subtests): } } } - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded - + decoded = FaunaDecoder.decode(test) + assert expected == decoded def test_encode_non_conflicting_at_prefix(subtests): - with subtests.test(msg="non-conflicting @ prefix"): + with subtests.test(msg="encode non-conflicting @ prefix"): test = {"@foo": 10} - expected = {"@foo": {"@int": "10"}} + expected = { + "object": { + "@foo": {"value": {"@int": "10"}} + } + } encoded = FaunaEncoder.encode(test) assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + with subtests.test(msg="decode non-conflicting @ prefix"): + expected = {"@foo": 10} + test = {"@foo": {"@int": "10"}} + decoded = FaunaDecoder.decode(test) + assert expected == decoded + + +def test_encode_complex_objects( + subtests, + complex_untyped_object, + complex_typed_object, + complex_wire_encoded_object, +): + with subtests.test(msg="encode array with nesting"): + doc_ref = DocumentReference.from_string("Array:123") + test = [1, ["hi"], doc_ref, fql("let d = ${foo}", foo=[{'inner': 3.1}]), {"foo": {"bar": 123}}] + expected = { + 'array': [ + {'value': {'@int': '1'}}, + {'array': [{'value': 'hi'}]}, + {'value': {'@ref': {'coll': {'@mod': 'Array'}, 'id': '123'}}}, + {'fql': ['let d = ', {'array': [{'object': {'inner': {'value': {'@double': '3.1'}}}}]}]}, + {'object': {'foo': {'object': {'bar': {'value': {'@int': '123'}}}}}} + ] + } + encoded = FaunaEncoder.encode(test) + assert expected == encoded -def test_encode_complex_objects(subtests, complex_untyped_object, - complex_typed_object): - - with subtests.test(msg="reasonable complex object"): + with subtests.test(msg="encode reasonable complex object"): encoded = FaunaEncoder.encode(complex_untyped_object) - assert encoded == complex_typed_object - decoded = FaunaDecoder.decode(encoded) + assert complex_wire_encoded_object == encoded + + with subtests.test(msg="decode reasonable complex object"): + decoded = FaunaDecoder.decode(complex_typed_object) assert complex_untyped_object == decoded - with subtests.test(msg="large list"): + with subtests.test(msg="encode large list"): test: Any = [10] * 10000 - expected = [{"@int": "10"}] * 10000 - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + FaunaEncoder.encode(test) + + with subtests.test(msg="decode large list"): + test = [{"@int": "10"}] * 10000 + FaunaDecoder.decode(test) - with subtests.test(msg="large dict"): + with subtests.test(msg="encode large dict"): test = {f"k{str(k)}": k for k in range(1, 10000)} - expected = {f"k{str(k)}": {"@int": str(k)} for k in range(1, 10000)} - encoded = FaunaEncoder.encode(test) - assert encoded == expected - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + FaunaEncoder.encode(test) + + with subtests.test(msg="decode large dict"): + test = {f"k{str(k)}": {"@int": str(k)} for k in range(1, 10000)} + FaunaDecoder.decode(test) # TODO(lucas): Fix max recursion bug to support deeper nesting - with subtests.test(msg="deep nesting in dict"): + with subtests.test(msg="encode deep nesting in dict"): test: Any = {"k1": "v"} + cur_node = test for i in range(2, 300): node: dict[str, Any] = {f"k{i}": "v"} cur_node[f"k{i}"] = node cur_node = node - encoded = FaunaEncoder.encode(test) - assert encoded == test - decoded = FaunaDecoder.decode(encoded) - assert test == decoded + FaunaEncoder.encode(test) + with subtests.test(msg="decode deep nesting in dict"): + test: Any = {"k1": "v"} + cur_node = test + for i in range(2, 300): + node: dict[str, Any] = {f"k{i}": "v"} + cur_node[f"k{i}"] = node + cur_node = node + + FaunaDecoder.decode(test) def test_encode_query_builder_strings(subtests): with subtests.test(msg="pure string query"): @@ -714,13 +731,13 @@ def test_encode_query_builder_with_value(subtests): expected = { "fql": [ "let x = ", { - 'value': { - 'name': 'Dino', + 'object': { + 'name': {'value': 'Dino'}, 'age': { - '@int': '0' + 'value': {'@int': '0'} }, 'birthdate': { - '@date': '2023-02-24' + 'value': {'@date': '2023-02-24'} } } } @@ -740,14 +757,14 @@ def test_encode_query_builder_sub_queries(subtests): "fql": [{ "fql": [ "let x = ", { - 'value': { - 'name': 'Dino', - 'age': { - '@int': '0' - }, - 'birthdate': { - '@date': '2023-02-24' - } + 'object': { + 'name': {'value': 'Dino'}, + 'age': { + 'value': {'@int': '0'} + }, + 'birthdate': { + 'value': {'@date': '2023-02-24'} + } } } ] From 9318ea9bbe7b5a80a041d445e5cd490e681a9633 Mon Sep 17 00:00:00 2001 From: Lucas Pedroza Date: Thu, 12 Oct 2023 13:29:41 +0200 Subject: [PATCH 2/2] yapf --- fauna/encoding/encoder.py | 17 ++- tests/integration/test_composition.py | 29 +++- tests/unit/conftest.py | 80 +++++++---- tests/unit/test_encoding.py | 186 ++++++++++++++++++++------ 4 files changed, 235 insertions(+), 77 deletions(-) diff --git a/fauna/encoding/encoder.py b/fauna/encoding/encoder.py index b3c8946e..8c9c916c 100644 --- a/fauna/encoding/encoder.py +++ b/fauna/encoding/encoder.py @@ -165,10 +165,15 @@ def _encode(o: Any, _markers: Optional[Set] = None): elif isinstance(o, date): return {"value": FaunaEncoder.from_date(o)} elif isinstance(o, Document): - return {"value": FaunaEncoder.from_doc_ref(DocumentReference(o.coll, o.id))} + return { + "value": FaunaEncoder.from_doc_ref(DocumentReference(o.coll, o.id)) + } elif isinstance(o, NamedDocument): - return {"value": FaunaEncoder.from_named_doc_ref( - NamedDocumentReference(o.coll, o.name))} + return { + "value": + FaunaEncoder.from_named_doc_ref( + NamedDocumentReference(o.coll, o.name)) + } elif isinstance(o, NullDocument): return FaunaEncoder.encode(o.ref) elif isinstance(o, (list, tuple)): @@ -196,4 +201,8 @@ def _encode_dict(dct, markers): raise ValueError("Circular reference detected") markers.add(id(dct)) - return {"object": {k: FaunaEncoder._encode(v, markers) for k, v in dct.items()}} + return { + "object": { + k: FaunaEncoder._encode(v, markers) for k, v in dct.items() + } + } diff --git a/tests/integration/test_composition.py b/tests/integration/test_composition.py index f8e70408..49e0b0c9 100644 --- a/tests/integration/test_composition.py +++ b/tests/integration/test_composition.py @@ -48,14 +48,37 @@ def update_doc_by_email(email: str, data: dict): def test_array_composition(client): - queries = [fql("1"), fql("2"), {"key": 3}, [fql("${inner}", inner={"inner": "thing"})]] + queries = [ + fql("1"), + fql("2"), { + "key": 3 + }, [fql("${inner}", inner={"inner": "thing"})] + ] q = fql("${queries}", queries=queries) res = client.query(q).data assert [1, 2, {'key': 3}, [{'inner': 'thing'}]] == res def test_object_composition(client): - queries = {1: fql("1"), 2: fql("2"), 3: {"key": fql("3")}, 4: {"inner": fql("${inner}", inner=["inner", "thing"])}} + queries = { + 1: fql("1"), + 2: fql("2"), + 3: { + "key": fql("3") + }, + 4: { + "inner": fql("${inner}", inner=["inner", "thing"]) + } + } q = fql("${queries}", queries=queries) res = client.query(q).data - assert {'1': 1, '2': 2, '3': {'key': 3}, '4': {'inner': ['inner', 'thing']}} == res + assert { + '1': 1, + '2': 2, + '3': { + 'key': 3 + }, + '4': { + 'inner': ['inner', 'thing'] + } + } == res diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 3c9e28d3..5849d519 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -147,12 +147,15 @@ def complex_typed_object(): }] } + @pytest.fixture def complex_wire_encoded_object(): return { 'object': { 'bugs_coll': { - 'value': {'@mod': 'Bugs'} + 'value': { + '@mod': 'Bugs' + } }, 'bug': { 'value': { @@ -164,22 +167,33 @@ def complex_wire_encoded_object(): } } }, - 'name': - {'value': 'fir'}, + 'name': { + 'value': 'fir' + }, 'age': { - 'value': {'@int': '200'} + 'value': { + '@int': '200' + } }, 'birthdate': { - 'value': {'@date': '1823-02-08'} + 'value': { + '@date': '1823-02-08' + } }, 'molecules': { - 'value': {'@long': '999999999999999999'} + 'value': { + '@long': '999999999999999999' + } }, 'circumference': { - 'value': {'@double': '3.82'} + 'value': { + '@double': '3.82' + } }, 'created_at': { - 'value': {'@time': '2003-02-08T13:28:12.000555+00:00'} + 'value': { + '@time': '2003-02-08T13:28:12.000555+00:00' + } }, 'extras': { 'object': { @@ -189,46 +203,60 @@ def complex_wire_encoded_object(): 'object': { 'egg': { 'object': { - 'fertilized': {'value': False} + 'fertilized': { + 'value': False + } } } } }, 'num_sticks': { - 'value': {'@int': '58'} + 'value': { + '@int': '58' + } }, } } } - }, 'measurements': { 'array': [{ 'object': { 'id': { - 'value': {'@int': '1'} + 'value': { + '@int': '1' + } }, 'employee': { - 'value': {'@int': '3'} + 'value': { + '@int': '3' + } }, 'time': { - 'value': {'@time': '2013-02-08T12:00:05.000123+00:00'} + 'value': { + '@time': '2013-02-08T12:00:05.000123+00:00' + } } } }, { - 'object': { - 'id': { - 'value': {'@int': '2'} - }, - 'employee': { - 'value': {'@int': '5'} - }, - 'time': { - 'value': {'@time': '2023-02-08T14:22:01.000001+00:00'} - } - } + 'object': { + 'id': { + 'value': { + '@int': '2' + } + }, + 'employee': { + 'value': { + '@int': '5' + } + }, + 'time': { + 'value': { + '@time': '2023-02-08T14:22:01.000001+00:00' + } + } + } }] } - } } diff --git a/tests/unit/test_encoding.py b/tests/unit/test_encoding.py index 9278e8bb..d6400a1c 100644 --- a/tests/unit/test_encoding.py +++ b/tests/unit/test_encoding.py @@ -143,7 +143,16 @@ def test_encode_document_references(subtests): doc_ref = DocumentReference.from_string("Col:123") with subtests.test(msg="encode/decode with @doc"): encoded = FaunaEncoder.encode(doc_ref) - assert {'value': {'@ref': {'coll': {'@mod': 'Col'}, 'id': "123"}}} == encoded + assert { + 'value': { + '@ref': { + 'coll': { + '@mod': 'Col' + }, + 'id': "123" + } + } + } == encoded decoded = FaunaDecoder.decode(encoded['value']) assert doc_ref == decoded @@ -178,7 +187,16 @@ def test_null_docments(subtests): with subtests.test(msg="encode named null doc"): null_doc = NullDocument( NamedDocumentReference("Collection", "Party"), "not found") - expected = {"value": {"@ref": {"name": "Party", "coll": {"@mod": "Collection"}}}} + expected = { + "value": { + "@ref": { + "name": "Party", + "coll": { + "@mod": "Collection" + } + } + } + } encoded = FaunaEncoder.encode(null_doc) assert expected == encoded @@ -203,7 +221,16 @@ def test_encode_named_document_references(subtests): doc_ref = NamedDocumentReference("Col", "Hi") with subtests.test(msg="encode/decode with @doc"): encoded = FaunaEncoder.encode(doc_ref) - assert {"value": {"@ref": {"name": "Hi", "coll": {"@mod": "Col"}}}} == encoded + assert { + "value": { + "@ref": { + "name": "Hi", + "coll": { + "@mod": "Col" + } + } + } + } == encoded decoded = FaunaDecoder.decode(encoded["value"]) assert doc_ref == decoded @@ -219,7 +246,16 @@ def test_encode_documents(subtests): id="123", coll="Dogs", ts=fixed_datetime, data={"name": "Scout"}) encoded = FaunaEncoder.encode(test) # should encode to a ref! - assert {"value": {"@ref": {"id": "123", "coll": {"@mod": "Dogs"}}}} == encoded + assert { + "value": { + "@ref": { + "id": "123", + "coll": { + "@mod": "Dogs" + } + } + } + } == encoded decoded = FaunaDecoder.decode(encoded["value"]) # refs will decode into references, not Documents assert DocumentReference("Dogs", "123") == decoded @@ -248,7 +284,16 @@ def test_encode_named_documents(subtests): test = NamedDocument(name="DogSchema", coll="Dogs", ts=fixed_datetime) encoded = FaunaEncoder.encode(test) # should encode to a ref! - assert {"value": {"@ref": {"name": "DogSchema", "coll": {"@mod": "Dogs"}}}} == encoded + assert { + "value": { + "@ref": { + "name": "DogSchema", + "coll": { + "@mod": "Dogs" + } + } + } + } == encoded decoded = FaunaDecoder.decode(encoded["value"]) # refs will decode into references, not Documents assert NamedDocumentReference("Dogs", "DogSchema") == decoded @@ -360,7 +405,6 @@ def test_encode_long_conflicts(subtests): encoded = FaunaEncoder.encode(test) assert encoded == expected - with subtests.test(msg="decode @long conflict with long type"): expected = {"@long": 2147483649} test = {"@object": {"@long": {"@long": "2147483649"}}} @@ -421,7 +465,7 @@ def test_encode_date_time_conflicts(subtests): assert {"@date": date(2023, 2, 28)} == decoded with subtests.test(msg="decode @date conflict with other type"): - test = {"@object": {"@date": "bar"}} + test = {"@object": {"@date": "bar"}} decoded = FaunaDecoder.decode(test) assert {"@date": "bar"} == decoded @@ -441,7 +485,9 @@ def test_encode_date_time_conflicts(subtests): expected = { "object": { "@time": { - "value": {"@time": "2023-02-28T10:10:10.000010+00:00"} + "value": { + "@time": "2023-02-28T10:10:10.000010+00:00" + } } } } @@ -451,7 +497,7 @@ def test_encode_date_time_conflicts(subtests): with subtests.test(msg="encode @time conflict with other type"): test = {"@time": "bar"} - expected = {"object": {"@time": {"value":"bar"}}} + expected = {"object": {"@time": {"value": "bar"}}} encoded = FaunaEncoder.encode(test) assert encoded == expected @@ -468,13 +514,7 @@ def test_encode_date_time_conflicts(subtests): 10, tzinfo=timezone(timedelta(0), '+00:00')) } - test = { - "@object": { - "@time": { - "@time": "2023-02-28T10:10:10.000010+00:00" - } - } - } + test = {"@object": {"@time": {"@time": "2023-02-28T10:10:10.000010+00:00"}}} decoded = FaunaDecoder.decode(test) assert expected == decoded @@ -571,7 +611,9 @@ def test_encode_nested_conflict(subtests): "@time": { "object": { "@long": { - "value": {"@int": "10"} + "value": { + "@int": "10" + } } } } @@ -608,15 +650,12 @@ def test_encode_nested_conflict(subtests): decoded = FaunaDecoder.decode(test) assert expected == decoded + def test_encode_non_conflicting_at_prefix(subtests): with subtests.test(msg="encode non-conflicting @ prefix"): test = {"@foo": 10} - expected = { - "object": { - "@foo": {"value": {"@int": "10"}} - } - } + expected = {"object": {"@foo": {"value": {"@int": "10"}}}} encoded = FaunaEncoder.encode(test) assert encoded == expected @@ -628,22 +667,68 @@ def test_encode_non_conflicting_at_prefix(subtests): def test_encode_complex_objects( - subtests, - complex_untyped_object, - complex_typed_object, - complex_wire_encoded_object, + subtests, + complex_untyped_object, + complex_typed_object, + complex_wire_encoded_object, ): with subtests.test(msg="encode array with nesting"): doc_ref = DocumentReference.from_string("Array:123") - test = [1, ["hi"], doc_ref, fql("let d = ${foo}", foo=[{'inner': 3.1}]), {"foo": {"bar": 123}}] + test = [ + 1, ["hi"], doc_ref, + fql("let d = ${foo}", foo=[{ + 'inner': 3.1 + }]), { + "foo": { + "bar": 123 + } + } + ] expected = { - 'array': [ - {'value': {'@int': '1'}}, - {'array': [{'value': 'hi'}]}, - {'value': {'@ref': {'coll': {'@mod': 'Array'}, 'id': '123'}}}, - {'fql': ['let d = ', {'array': [{'object': {'inner': {'value': {'@double': '3.1'}}}}]}]}, - {'object': {'foo': {'object': {'bar': {'value': {'@int': '123'}}}}}} - ] + 'array': [{ + 'value': { + '@int': '1' + } + }, { + 'array': [{ + 'value': 'hi' + }] + }, { + 'value': { + '@ref': { + 'coll': { + '@mod': 'Array' + }, + 'id': '123' + } + } + }, { + 'fql': [ + 'let d = ', { + 'array': [{ + 'object': { + 'inner': { + 'value': { + '@double': '3.1' + } + } + } + }] + } + ] + }, { + 'object': { + 'foo': { + 'object': { + 'bar': { + 'value': { + '@int': '123' + } + } + } + } + } + }] } encoded = FaunaEncoder.encode(test) assert expected == encoded @@ -694,6 +779,7 @@ def test_encode_complex_objects( FaunaDecoder.decode(test) + def test_encode_query_builder_strings(subtests): with subtests.test(msg="pure string query"): actual = FaunaEncoder.encode(fql("let x = 11")) @@ -732,12 +818,18 @@ def test_encode_query_builder_with_value(subtests): "fql": [ "let x = ", { 'object': { - 'name': {'value': 'Dino'}, + 'name': { + 'value': 'Dino' + }, 'age': { - 'value': {'@int': '0'} + 'value': { + '@int': '0' + } }, 'birthdate': { - 'value': {'@date': '2023-02-24'} + 'value': { + '@date': '2023-02-24' + } } } } @@ -758,13 +850,19 @@ def test_encode_query_builder_sub_queries(subtests): "fql": [ "let x = ", { 'object': { - 'name': {'value': 'Dino'}, - 'age': { - 'value': {'@int': '0'} - }, - 'birthdate': { - 'value': {'@date': '2023-02-24'} - } + 'name': { + 'value': 'Dino' + }, + 'age': { + 'value': { + '@int': '0' + } + }, + 'birthdate': { + 'value': { + '@date': '2023-02-24' + } + } } } ]