Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for array and object encoding #152

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 45 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:
+-------------------------------+---------------+
Copy link

@macmv macmv Oct 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can't comment on it, but _RESERVED_TAGS, above this, is no longer used.

| 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,37 @@ 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 +192,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 +201,8 @@ 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()
}
}
37 changes: 37 additions & 0 deletions tests/integration/test_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,40 @@ 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
114 changes: 114 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,117 @@ 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