fauna.encoding.encoder

  1import base64
  2from datetime import datetime, date
  3from typing import Any, Optional, List, Union
  4
  5from fauna.query.models import DocumentReference, Module, Document, NamedDocument, NamedDocumentReference, NullDocument, \
  6  StreamToken
  7from fauna.query.query_builder import Query, Fragment, LiteralFragment, ValueFragment
  8
  9_RESERVED_TAGS = [
 10    "@date",
 11    "@doc",
 12    "@double",
 13    "@int",
 14    "@long",
 15    "@mod",
 16    "@object",
 17    "@ref",
 18    "@set",
 19    "@time",
 20]
 21
 22
 23class FaunaEncoder:
 24  """Supports the following types:
 25
 26    +-------------------------------+---------------+
 27    | Python                        | Fauna Tags    |
 28    +===============================+===============+
 29    | dict                          | @object       |
 30    +-------------------------------+---------------+
 31    | list, tuple                   | array         |
 32    +-------------------------------+---------------+
 33    | str                           | string        |
 34    +-------------------------------+---------------+
 35    | int 32-bit signed             | @int          |
 36    +-------------------------------+---------------+
 37    | int 64-bit signed             | @long         |
 38    +-------------------------------+---------------+
 39    | float                         | @double       |
 40    +-------------------------------+---------------+
 41    | datetime.datetime             | @time         |
 42    +-------------------------------+---------------+
 43    | datetime.date                 | @date         |
 44    +-------------------------------+---------------+
 45    | True                          | True          |
 46    +-------------------------------+---------------+
 47    | False                         | False         |
 48    +-------------------------------+---------------+
 49    | None                          | None          |
 50    +-------------------------------+---------------+
 51    | bytes / bytearray             | @bytes        |
 52    +-------------------------------+---------------+
 53    | *Document                     | @ref          |
 54    +-------------------------------+---------------+
 55    | *DocumentReference            | @ref          |
 56    +-------------------------------+---------------+
 57    | Module                        | @mod          |
 58    +-------------------------------+---------------+
 59    | Query                         | fql           |
 60    +-------------------------------+---------------+
 61    | ValueFragment                 | value         |
 62    +-------------------------------+---------------+
 63    | TemplateFragment              | string        |
 64    +-------------------------------+---------------+
 65    | StreamToken                   | string        |
 66    +-------------------------------+---------------+
 67
 68    """
 69
 70  @staticmethod
 71  def encode(obj: Any) -> Any:
 72    """Encodes supported objects into the tagged format.
 73
 74        Examples:
 75            - Up to 32-bit ints encode to { "@int": "..." }
 76            - Up to 64-bit ints encode to { "@long": "..." }
 77            - Floats encode to { "@double": "..." }
 78            - datetime encodes to { "@time": "..." }
 79            - date encodes to { "@date": "..." }
 80            - DocumentReference encodes to { "@doc": "..." }
 81            - Module encodes to { "@mod": "..." }
 82            - Query encodes to { "fql": [...] }
 83            - ValueFragment encodes to { "value": <encoded_val> }
 84            - LiteralFragment encodes to a string
 85            - StreamToken encodes to a string
 86
 87        :raises ValueError: If value cannot be encoded, cannot be encoded safely, or there's a circular reference.
 88        :param obj: the object to decode
 89        """
 90    return FaunaEncoder._encode(obj)
 91
 92  @staticmethod
 93  def from_int(obj: int):
 94    if -2**31 <= obj <= 2**31 - 1:
 95      return {"@int": repr(obj)}
 96    elif -2**63 <= obj <= 2**63 - 1:
 97      return {"@long": repr(obj)}
 98    else:
 99      raise ValueError("Precision loss when converting int to Fauna type")
100
101  @staticmethod
102  def from_bool(obj: bool):
103    return obj
104
105  @staticmethod
106  def from_float(obj: float):
107    return {"@double": repr(obj)}
108
109  @staticmethod
110  def from_str(obj: str):
111    return obj
112
113  @staticmethod
114  def from_datetime(obj: datetime):
115    if obj.utcoffset() is None:
116      raise ValueError("datetimes must be timezone-aware")
117
118    return {"@time": obj.isoformat(sep="T")}
119
120  @staticmethod
121  def from_date(obj: date):
122    return {"@date": obj.isoformat()}
123
124  @staticmethod
125  def from_bytes(obj: Union[bytearray, bytes]):
126    return {"@bytes": base64.b64encode(obj).decode('ascii')}
127
128  @staticmethod
129  def from_doc_ref(obj: DocumentReference):
130    return {"@ref": {"id": obj.id, "coll": FaunaEncoder.from_mod(obj.coll)}}
131
132  @staticmethod
133  def from_named_doc_ref(obj: NamedDocumentReference):
134    return {"@ref": {"name": obj.name, "coll": FaunaEncoder.from_mod(obj.coll)}}
135
136  @staticmethod
137  def from_mod(obj: Module):
138    return {"@mod": obj.name}
139
140  @staticmethod
141  def from_dict(obj: Any):
142    return {"@object": obj}
143
144  @staticmethod
145  def from_none():
146    return None
147
148  @staticmethod
149  def from_fragment(obj: Fragment):
150    if isinstance(obj, LiteralFragment):
151      return obj.get()
152    elif isinstance(obj, ValueFragment):
153      v = obj.get()
154      if isinstance(v, Query):
155        return FaunaEncoder.from_query_interpolation_builder(v)
156      else:
157        return {"value": FaunaEncoder.encode(v)}
158    else:
159      raise ValueError(f"Unknown fragment type: {type(obj)}")
160
161  @staticmethod
162  def from_query_interpolation_builder(obj: Query):
163    return {"fql": [FaunaEncoder.from_fragment(f) for f in obj.fragments]}
164
165  @staticmethod
166  def from_streamtoken(obj: StreamToken):
167    return {"@stream": obj.token}
168
169  @staticmethod
170  def _encode(o: Any, _markers: Optional[List] = None):
171    if _markers is None:
172      _markers = []
173
174    if isinstance(o, str):
175      return FaunaEncoder.from_str(o)
176    elif o is None:
177      return FaunaEncoder.from_none()
178    elif o is True:
179      return FaunaEncoder.from_bool(o)
180    elif o is False:
181      return FaunaEncoder.from_bool(o)
182    elif isinstance(o, int):
183      return FaunaEncoder.from_int(o)
184    elif isinstance(o, float):
185      return FaunaEncoder.from_float(o)
186    elif isinstance(o, Module):
187      return FaunaEncoder.from_mod(o)
188    elif isinstance(o, DocumentReference):
189      return FaunaEncoder.from_doc_ref(o)
190    elif isinstance(o, NamedDocumentReference):
191      return FaunaEncoder.from_named_doc_ref(o)
192    elif isinstance(o, datetime):
193      return FaunaEncoder.from_datetime(o)
194    elif isinstance(o, date):
195      return FaunaEncoder.from_date(o)
196    elif isinstance(o, bytearray) or isinstance(o, bytes):
197      return FaunaEncoder.from_bytes(o)
198    elif isinstance(o, Document):
199      return FaunaEncoder.from_doc_ref(DocumentReference(o.coll, o.id))
200    elif isinstance(o, NamedDocument):
201      return FaunaEncoder.from_named_doc_ref(
202          NamedDocumentReference(o.coll, o.name))
203    elif isinstance(o, NullDocument):
204      return FaunaEncoder.encode(o.ref)
205    elif isinstance(o, (list, tuple)):
206      return FaunaEncoder._encode_list(o, _markers)
207    elif isinstance(o, dict):
208      return FaunaEncoder._encode_dict(o, _markers)
209    elif isinstance(o, Query):
210      return FaunaEncoder.from_query_interpolation_builder(o)
211    elif isinstance(o, StreamToken):
212      return FaunaEncoder.from_streamtoken(o)
213    else:
214      raise ValueError(f"Object {o} of type {type(o)} cannot be encoded")
215
216  @staticmethod
217  def _encode_list(lst, markers):
218    _id = id(lst)
219    if _id in markers:
220      raise ValueError("Circular reference detected")
221
222    markers.append(id(lst))
223    res = [FaunaEncoder._encode(elem, markers) for elem in lst]
224    markers.pop()
225    return res
226
227  @staticmethod
228  def _encode_dict(dct, markers):
229    _id = id(dct)
230    if _id in markers:
231      raise ValueError("Circular reference detected")
232
233    markers.append(id(dct))
234    if any(i in _RESERVED_TAGS for i in dct.keys()):
235      res = {
236          "@object": {
237              k: FaunaEncoder._encode(v, markers) for k, v in dct.items()
238          }
239      }
240      markers.pop()
241      return res
242    else:
243      res = {k: FaunaEncoder._encode(v, markers) for k, v in dct.items()}
244      markers.pop()
245      return res
class FaunaEncoder:
 24class FaunaEncoder:
 25  """Supports the following types:
 26
 27    +-------------------------------+---------------+
 28    | Python                        | Fauna Tags    |
 29    +===============================+===============+
 30    | dict                          | @object       |
 31    +-------------------------------+---------------+
 32    | list, tuple                   | array         |
 33    +-------------------------------+---------------+
 34    | str                           | string        |
 35    +-------------------------------+---------------+
 36    | int 32-bit signed             | @int          |
 37    +-------------------------------+---------------+
 38    | int 64-bit signed             | @long         |
 39    +-------------------------------+---------------+
 40    | float                         | @double       |
 41    +-------------------------------+---------------+
 42    | datetime.datetime             | @time         |
 43    +-------------------------------+---------------+
 44    | datetime.date                 | @date         |
 45    +-------------------------------+---------------+
 46    | True                          | True          |
 47    +-------------------------------+---------------+
 48    | False                         | False         |
 49    +-------------------------------+---------------+
 50    | None                          | None          |
 51    +-------------------------------+---------------+
 52    | bytes / bytearray             | @bytes        |
 53    +-------------------------------+---------------+
 54    | *Document                     | @ref          |
 55    +-------------------------------+---------------+
 56    | *DocumentReference            | @ref          |
 57    +-------------------------------+---------------+
 58    | Module                        | @mod          |
 59    +-------------------------------+---------------+
 60    | Query                         | fql           |
 61    +-------------------------------+---------------+
 62    | ValueFragment                 | value         |
 63    +-------------------------------+---------------+
 64    | TemplateFragment              | string        |
 65    +-------------------------------+---------------+
 66    | StreamToken                   | string        |
 67    +-------------------------------+---------------+
 68
 69    """
 70
 71  @staticmethod
 72  def encode(obj: Any) -> Any:
 73    """Encodes supported objects into the tagged format.
 74
 75        Examples:
 76            - Up to 32-bit ints encode to { "@int": "..." }
 77            - Up to 64-bit ints encode to { "@long": "..." }
 78            - Floats encode to { "@double": "..." }
 79            - datetime encodes to { "@time": "..." }
 80            - date encodes to { "@date": "..." }
 81            - DocumentReference encodes to { "@doc": "..." }
 82            - Module encodes to { "@mod": "..." }
 83            - Query encodes to { "fql": [...] }
 84            - ValueFragment encodes to { "value": <encoded_val> }
 85            - LiteralFragment encodes to a string
 86            - StreamToken encodes to a string
 87
 88        :raises ValueError: If value cannot be encoded, cannot be encoded safely, or there's a circular reference.
 89        :param obj: the object to decode
 90        """
 91    return FaunaEncoder._encode(obj)
 92
 93  @staticmethod
 94  def from_int(obj: int):
 95    if -2**31 <= obj <= 2**31 - 1:
 96      return {"@int": repr(obj)}
 97    elif -2**63 <= obj <= 2**63 - 1:
 98      return {"@long": repr(obj)}
 99    else:
100      raise ValueError("Precision loss when converting int to Fauna type")
101
102  @staticmethod
103  def from_bool(obj: bool):
104    return obj
105
106  @staticmethod
107  def from_float(obj: float):
108    return {"@double": repr(obj)}
109
110  @staticmethod
111  def from_str(obj: str):
112    return obj
113
114  @staticmethod
115  def from_datetime(obj: datetime):
116    if obj.utcoffset() is None:
117      raise ValueError("datetimes must be timezone-aware")
118
119    return {"@time": obj.isoformat(sep="T")}
120
121  @staticmethod
122  def from_date(obj: date):
123    return {"@date": obj.isoformat()}
124
125  @staticmethod
126  def from_bytes(obj: Union[bytearray, bytes]):
127    return {"@bytes": base64.b64encode(obj).decode('ascii')}
128
129  @staticmethod
130  def from_doc_ref(obj: DocumentReference):
131    return {"@ref": {"id": obj.id, "coll": FaunaEncoder.from_mod(obj.coll)}}
132
133  @staticmethod
134  def from_named_doc_ref(obj: NamedDocumentReference):
135    return {"@ref": {"name": obj.name, "coll": FaunaEncoder.from_mod(obj.coll)}}
136
137  @staticmethod
138  def from_mod(obj: Module):
139    return {"@mod": obj.name}
140
141  @staticmethod
142  def from_dict(obj: Any):
143    return {"@object": obj}
144
145  @staticmethod
146  def from_none():
147    return None
148
149  @staticmethod
150  def from_fragment(obj: Fragment):
151    if isinstance(obj, LiteralFragment):
152      return obj.get()
153    elif isinstance(obj, ValueFragment):
154      v = obj.get()
155      if isinstance(v, Query):
156        return FaunaEncoder.from_query_interpolation_builder(v)
157      else:
158        return {"value": FaunaEncoder.encode(v)}
159    else:
160      raise ValueError(f"Unknown fragment type: {type(obj)}")
161
162  @staticmethod
163  def from_query_interpolation_builder(obj: Query):
164    return {"fql": [FaunaEncoder.from_fragment(f) for f in obj.fragments]}
165
166  @staticmethod
167  def from_streamtoken(obj: StreamToken):
168    return {"@stream": obj.token}
169
170  @staticmethod
171  def _encode(o: Any, _markers: Optional[List] = None):
172    if _markers is None:
173      _markers = []
174
175    if isinstance(o, str):
176      return FaunaEncoder.from_str(o)
177    elif o is None:
178      return FaunaEncoder.from_none()
179    elif o is True:
180      return FaunaEncoder.from_bool(o)
181    elif o is False:
182      return FaunaEncoder.from_bool(o)
183    elif isinstance(o, int):
184      return FaunaEncoder.from_int(o)
185    elif isinstance(o, float):
186      return FaunaEncoder.from_float(o)
187    elif isinstance(o, Module):
188      return FaunaEncoder.from_mod(o)
189    elif isinstance(o, DocumentReference):
190      return FaunaEncoder.from_doc_ref(o)
191    elif isinstance(o, NamedDocumentReference):
192      return FaunaEncoder.from_named_doc_ref(o)
193    elif isinstance(o, datetime):
194      return FaunaEncoder.from_datetime(o)
195    elif isinstance(o, date):
196      return FaunaEncoder.from_date(o)
197    elif isinstance(o, bytearray) or isinstance(o, bytes):
198      return FaunaEncoder.from_bytes(o)
199    elif isinstance(o, Document):
200      return FaunaEncoder.from_doc_ref(DocumentReference(o.coll, o.id))
201    elif isinstance(o, NamedDocument):
202      return FaunaEncoder.from_named_doc_ref(
203          NamedDocumentReference(o.coll, o.name))
204    elif isinstance(o, NullDocument):
205      return FaunaEncoder.encode(o.ref)
206    elif isinstance(o, (list, tuple)):
207      return FaunaEncoder._encode_list(o, _markers)
208    elif isinstance(o, dict):
209      return FaunaEncoder._encode_dict(o, _markers)
210    elif isinstance(o, Query):
211      return FaunaEncoder.from_query_interpolation_builder(o)
212    elif isinstance(o, StreamToken):
213      return FaunaEncoder.from_streamtoken(o)
214    else:
215      raise ValueError(f"Object {o} of type {type(o)} cannot be encoded")
216
217  @staticmethod
218  def _encode_list(lst, markers):
219    _id = id(lst)
220    if _id in markers:
221      raise ValueError("Circular reference detected")
222
223    markers.append(id(lst))
224    res = [FaunaEncoder._encode(elem, markers) for elem in lst]
225    markers.pop()
226    return res
227
228  @staticmethod
229  def _encode_dict(dct, markers):
230    _id = id(dct)
231    if _id in markers:
232      raise ValueError("Circular reference detected")
233
234    markers.append(id(dct))
235    if any(i in _RESERVED_TAGS for i in dct.keys()):
236      res = {
237          "@object": {
238              k: FaunaEncoder._encode(v, markers) for k, v in dct.items()
239          }
240      }
241      markers.pop()
242      return res
243    else:
244      res = {k: FaunaEncoder._encode(v, markers) for k, v in dct.items()}
245      markers.pop()
246      return res

Supports the following types:

+-------------------------------+---------------+ | Python | Fauna Tags | +===============================+===============+ | dict | @object | +-------------------------------+---------------+ | list, tuple | array | +-------------------------------+---------------+ | str | string | +-------------------------------+---------------+ | int 32-bit signed | @int | +-------------------------------+---------------+ | int 64-bit signed | @long | +-------------------------------+---------------+ | float | @double | +-------------------------------+---------------+ | datetime.datetime | @time | +-------------------------------+---------------+ | datetime.date | @date | +-------------------------------+---------------+ | True | True | +-------------------------------+---------------+ | False | False | +-------------------------------+---------------+ | None | None | +-------------------------------+---------------+ | bytes / bytearray | @bytes | +-------------------------------+---------------+ | *Document | @ref | +-------------------------------+---------------+ | *DocumentReference | @ref | +-------------------------------+---------------+ | Module | @mod | +-------------------------------+---------------+ | Query | fql | +-------------------------------+---------------+ | ValueFragment | value | +-------------------------------+---------------+ | TemplateFragment | string | +-------------------------------+---------------+ | StreamToken | string | +-------------------------------+---------------+

@staticmethod
def encode(obj: Any) -> Any:
71  @staticmethod
72  def encode(obj: Any) -> Any:
73    """Encodes supported objects into the tagged format.
74
75        Examples:
76            - Up to 32-bit ints encode to { "@int": "..." }
77            - Up to 64-bit ints encode to { "@long": "..." }
78            - Floats encode to { "@double": "..." }
79            - datetime encodes to { "@time": "..." }
80            - date encodes to { "@date": "..." }
81            - DocumentReference encodes to { "@doc": "..." }
82            - Module encodes to { "@mod": "..." }
83            - Query encodes to { "fql": [...] }
84            - ValueFragment encodes to { "value": <encoded_val> }
85            - LiteralFragment encodes to a string
86            - StreamToken encodes to a string
87
88        :raises ValueError: If value cannot be encoded, cannot be encoded safely, or there's a circular reference.
89        :param obj: the object to decode
90        """
91    return FaunaEncoder._encode(obj)

Encodes supported objects into the tagged format.

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": "..." } - Query encodes to { "fql": [...] } - ValueFragment encodes to { "value": } - LiteralFragment encodes to a string - StreamToken encodes to a string

Raises
  • ValueError: If value cannot be encoded, cannot be encoded safely, or there's a circular reference.
Parameters
  • obj: the object to decode
@staticmethod
def from_int(obj: int):
 93  @staticmethod
 94  def from_int(obj: int):
 95    if -2**31 <= obj <= 2**31 - 1:
 96      return {"@int": repr(obj)}
 97    elif -2**63 <= obj <= 2**63 - 1:
 98      return {"@long": repr(obj)}
 99    else:
100      raise ValueError("Precision loss when converting int to Fauna type")
@staticmethod
def from_bool(obj: bool):
102  @staticmethod
103  def from_bool(obj: bool):
104    return obj
@staticmethod
def from_float(obj: float):
106  @staticmethod
107  def from_float(obj: float):
108    return {"@double": repr(obj)}
@staticmethod
def from_str(obj: str):
110  @staticmethod
111  def from_str(obj: str):
112    return obj
@staticmethod
def from_datetime(obj: datetime.datetime):
114  @staticmethod
115  def from_datetime(obj: datetime):
116    if obj.utcoffset() is None:
117      raise ValueError("datetimes must be timezone-aware")
118
119    return {"@time": obj.isoformat(sep="T")}
@staticmethod
def from_date(obj: datetime.date):
121  @staticmethod
122  def from_date(obj: date):
123    return {"@date": obj.isoformat()}
@staticmethod
def from_bytes(obj: Union[bytearray, bytes]):
125  @staticmethod
126  def from_bytes(obj: Union[bytearray, bytes]):
127    return {"@bytes": base64.b64encode(obj).decode('ascii')}
@staticmethod
def from_doc_ref(obj: fauna.query.models.DocumentReference):
129  @staticmethod
130  def from_doc_ref(obj: DocumentReference):
131    return {"@ref": {"id": obj.id, "coll": FaunaEncoder.from_mod(obj.coll)}}
@staticmethod
def from_named_doc_ref(obj: fauna.query.models.NamedDocumentReference):
133  @staticmethod
134  def from_named_doc_ref(obj: NamedDocumentReference):
135    return {"@ref": {"name": obj.name, "coll": FaunaEncoder.from_mod(obj.coll)}}
@staticmethod
def from_mod(obj: fauna.query.models.Module):
137  @staticmethod
138  def from_mod(obj: Module):
139    return {"@mod": obj.name}
@staticmethod
def from_dict(obj: Any):
141  @staticmethod
142  def from_dict(obj: Any):
143    return {"@object": obj}
@staticmethod
def from_none():
145  @staticmethod
146  def from_none():
147    return None
@staticmethod
def from_fragment(obj: fauna.query.query_builder.Fragment):
149  @staticmethod
150  def from_fragment(obj: Fragment):
151    if isinstance(obj, LiteralFragment):
152      return obj.get()
153    elif isinstance(obj, ValueFragment):
154      v = obj.get()
155      if isinstance(v, Query):
156        return FaunaEncoder.from_query_interpolation_builder(v)
157      else:
158        return {"value": FaunaEncoder.encode(v)}
159    else:
160      raise ValueError(f"Unknown fragment type: {type(obj)}")
@staticmethod
def from_query_interpolation_builder(obj: fauna.query.query_builder.Query):
162  @staticmethod
163  def from_query_interpolation_builder(obj: Query):
164    return {"fql": [FaunaEncoder.from_fragment(f) for f in obj.fragments]}
@staticmethod
def from_streamtoken(obj: fauna.query.models.StreamToken):
166  @staticmethod
167  def from_streamtoken(obj: StreamToken):
168    return {"@stream": obj.token}