Skip to content

Commit

Permalink
Add support for array and object encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
pnwpedro committed Oct 12, 2023
1 parent 196bf41 commit e92ba07
Show file tree
Hide file tree
Showing 4 changed files with 403 additions and 307 deletions.
93 changes: 36 additions & 57 deletions fauna/encoding/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,58 +24,52 @@ 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 |
+-------------------------------+---------------+
| Module | @mod |
+-------------------------------+---------------+
| 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": <encoded_val> }
- 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
Expand Down Expand Up @@ -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
Expand All @@ -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)}")

Expand All @@ -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)):
Expand All @@ -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):
Expand All @@ -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()}}
14 changes: 14 additions & 0 deletions tests/integration/test_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
86 changes: 86 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
}
}
}]
}

}
}
Loading

0 comments on commit e92ba07

Please sign in to comment.