fauna.encoding.decoder

  1import base64
  2from typing import Any, List, Union
  3
  4from iso8601 import parse_date
  5
  6from fauna.query.models import Module, DocumentReference, Document, NamedDocument, NamedDocumentReference, Page, \
  7  NullDocument, StreamToken
  8
  9
 10class FaunaDecoder:
 11  """Supports the following types:
 12
 13     +--------------------+---------------+
 14     | Python             | Fauna         |
 15     +====================+===============+
 16     | dict               | object        |
 17     +--------------------+---------------+
 18     | list, tuple        | array         |
 19     +--------------------+---------------+
 20     | str                | string        |
 21     +--------------------+---------------+
 22     | int                | @int          |
 23     +--------------------+---------------+
 24     | int                | @long         |
 25     +--------------------+---------------+
 26     | float              | @double       |
 27     +--------------------+---------------+
 28     | datetime.datetime  | @time         |
 29     +--------------------+---------------+
 30     | datetime.date      | @date         |
 31     +--------------------+---------------+
 32     | True               | true          |
 33     +--------------------+---------------+
 34     | False              | false         |
 35     +--------------------+---------------+
 36     | None               | null          |
 37     +--------------------+---------------+
 38     | bytearray          | @bytes        |
 39     +--------------------+---------------+
 40     | *DocumentReference | @ref          |
 41     +--------------------+---------------+
 42     | *Document          | @doc          |
 43     +--------------------+---------------+
 44     | Module             | @mod          |
 45     +--------------------+---------------+
 46     | Page               | @set          |
 47     +--------------------+---------------+
 48     | StreamToken        | @stream       |
 49     +--------------------+---------------+
 50
 51     """
 52
 53  @staticmethod
 54  def decode(obj: Any):
 55    """Decodes supported objects from the tagged typed into untagged.
 56
 57        Examples:
 58            - { "@int": "100" } decodes to 100 of type int
 59            - { "@double": "100" } decodes to 100.0 of type float
 60            - { "@long": "100" } decodes to 100 of type int
 61            - { "@time": "..." } decodes to a datetime
 62            - { "@date": "..." } decodes to a date
 63            - { "@doc": ... } decodes to a Document or NamedDocument
 64            - { "@ref": ... } decodes to a DocumentReference or NamedDocumentReference
 65            - { "@mod": ... } decodes to a Module
 66            - { "@set": ... } decodes to a Page
 67            - { "@stream": ... } decodes to a StreamToken
 68            - { "@bytes": ... } decodes to a bytearray
 69
 70        :param obj: the object to decode
 71        """
 72    return FaunaDecoder._decode(obj)
 73
 74  @staticmethod
 75  def _decode(o: Any, escaped: bool = False):
 76    if isinstance(o, (str, bool, int, float)):
 77      return o
 78    elif isinstance(o, list):
 79      return FaunaDecoder._decode_list(o)
 80    elif isinstance(o, dict):
 81      return FaunaDecoder._decode_dict(o, escaped)
 82
 83  @staticmethod
 84  def _decode_list(lst: List):
 85    return [FaunaDecoder._decode(i) for i in lst]
 86
 87  @staticmethod
 88  def _decode_dict(dct: dict, escaped: bool):
 89    keys = dct.keys()
 90
 91    # If escaped, everything is user-specified
 92    if escaped:
 93      return {k: FaunaDecoder._decode(v) for k, v in dct.items()}
 94
 95    if len(keys) == 1:
 96      if "@int" in keys:
 97        return int(dct["@int"])
 98      if "@long" in keys:
 99        return int(dct["@long"])
100      if "@double" in dct:
101        return float(dct["@double"])
102      if "@object" in dct:
103        return FaunaDecoder._decode(dct["@object"], True)
104      if "@mod" in dct:
105        return Module(dct["@mod"])
106      if "@time" in dct:
107        return parse_date(dct["@time"])
108      if "@date" in dct:
109        return parse_date(dct["@date"]).date()
110      if "@bytes" in dct:
111        bts = base64.b64decode(dct["@bytes"])
112        return bytearray(bts)
113      if "@doc" in dct:
114        value = dct["@doc"]
115        if isinstance(value, str):
116          # Not distinguishing between DocumentReference and NamedDocumentReference because this shouldn't
117          # be an issue much longer
118          return DocumentReference.from_string(value)
119
120        contents = FaunaDecoder._decode(value)
121
122        if "id" in contents and "coll" in contents and "ts" in contents:
123          doc_id = contents.pop("id")
124          doc_coll = contents.pop("coll")
125          doc_ts = contents.pop("ts")
126
127          return Document(
128              id=doc_id,
129              coll=doc_coll,
130              ts=doc_ts,
131              data=contents,
132          )
133        elif "name" in contents and "coll" in contents and "ts" in contents:
134          doc_name = contents.pop("name")
135          doc_coll = contents.pop("coll")
136          doc_ts = contents.pop("ts")
137
138          return NamedDocument(
139              name=doc_name,
140              coll=doc_coll,
141              ts=doc_ts,
142              data=contents,
143          )
144        else:
145          # Unsupported document reference. Return the unwrapped value to futureproof.
146          return contents
147
148      if "@ref" in dct:
149        value = dct["@ref"]
150        if "id" not in value and "name" not in value:
151          # Unsupported document reference. Return the unwrapped value to futureproof.
152          return value
153
154        col = FaunaDecoder._decode(value["coll"])
155        doc_ref: Union[DocumentReference, NamedDocumentReference]
156
157        if "id" in value:
158          doc_ref = DocumentReference(col, value["id"])
159        else:
160          doc_ref = NamedDocumentReference(col, value["name"])
161
162        if "exists" in value and not value["exists"]:
163          cause = value["cause"] if "cause" in value else None
164          return NullDocument(doc_ref, cause)
165
166        return doc_ref
167
168      if "@set" in dct:
169        value = dct["@set"]
170        if isinstance(value, str):
171          return Page(after=value)
172
173        after = value["after"] if "after" in value else None
174        data = FaunaDecoder._decode(value["data"]) if "data" in value else None
175
176        return Page(data=data, after=after)
177
178      if "@stream" in dct:
179        return StreamToken(dct["@stream"])
180
181    return {k: FaunaDecoder._decode(v) for k, v in dct.items()}
class FaunaDecoder:
 11class FaunaDecoder:
 12  """Supports the following types:
 13
 14     +--------------------+---------------+
 15     | Python             | Fauna         |
 16     +====================+===============+
 17     | dict               | object        |
 18     +--------------------+---------------+
 19     | list, tuple        | array         |
 20     +--------------------+---------------+
 21     | str                | string        |
 22     +--------------------+---------------+
 23     | int                | @int          |
 24     +--------------------+---------------+
 25     | int                | @long         |
 26     +--------------------+---------------+
 27     | float              | @double       |
 28     +--------------------+---------------+
 29     | datetime.datetime  | @time         |
 30     +--------------------+---------------+
 31     | datetime.date      | @date         |
 32     +--------------------+---------------+
 33     | True               | true          |
 34     +--------------------+---------------+
 35     | False              | false         |
 36     +--------------------+---------------+
 37     | None               | null          |
 38     +--------------------+---------------+
 39     | bytearray          | @bytes        |
 40     +--------------------+---------------+
 41     | *DocumentReference | @ref          |
 42     +--------------------+---------------+
 43     | *Document          | @doc          |
 44     +--------------------+---------------+
 45     | Module             | @mod          |
 46     +--------------------+---------------+
 47     | Page               | @set          |
 48     +--------------------+---------------+
 49     | StreamToken        | @stream       |
 50     +--------------------+---------------+
 51
 52     """
 53
 54  @staticmethod
 55  def decode(obj: Any):
 56    """Decodes supported objects from the tagged typed into untagged.
 57
 58        Examples:
 59            - { "@int": "100" } decodes to 100 of type int
 60            - { "@double": "100" } decodes to 100.0 of type float
 61            - { "@long": "100" } decodes to 100 of type int
 62            - { "@time": "..." } decodes to a datetime
 63            - { "@date": "..." } decodes to a date
 64            - { "@doc": ... } decodes to a Document or NamedDocument
 65            - { "@ref": ... } decodes to a DocumentReference or NamedDocumentReference
 66            - { "@mod": ... } decodes to a Module
 67            - { "@set": ... } decodes to a Page
 68            - { "@stream": ... } decodes to a StreamToken
 69            - { "@bytes": ... } decodes to a bytearray
 70
 71        :param obj: the object to decode
 72        """
 73    return FaunaDecoder._decode(obj)
 74
 75  @staticmethod
 76  def _decode(o: Any, escaped: bool = False):
 77    if isinstance(o, (str, bool, int, float)):
 78      return o
 79    elif isinstance(o, list):
 80      return FaunaDecoder._decode_list(o)
 81    elif isinstance(o, dict):
 82      return FaunaDecoder._decode_dict(o, escaped)
 83
 84  @staticmethod
 85  def _decode_list(lst: List):
 86    return [FaunaDecoder._decode(i) for i in lst]
 87
 88  @staticmethod
 89  def _decode_dict(dct: dict, escaped: bool):
 90    keys = dct.keys()
 91
 92    # If escaped, everything is user-specified
 93    if escaped:
 94      return {k: FaunaDecoder._decode(v) for k, v in dct.items()}
 95
 96    if len(keys) == 1:
 97      if "@int" in keys:
 98        return int(dct["@int"])
 99      if "@long" in keys:
100        return int(dct["@long"])
101      if "@double" in dct:
102        return float(dct["@double"])
103      if "@object" in dct:
104        return FaunaDecoder._decode(dct["@object"], True)
105      if "@mod" in dct:
106        return Module(dct["@mod"])
107      if "@time" in dct:
108        return parse_date(dct["@time"])
109      if "@date" in dct:
110        return parse_date(dct["@date"]).date()
111      if "@bytes" in dct:
112        bts = base64.b64decode(dct["@bytes"])
113        return bytearray(bts)
114      if "@doc" in dct:
115        value = dct["@doc"]
116        if isinstance(value, str):
117          # Not distinguishing between DocumentReference and NamedDocumentReference because this shouldn't
118          # be an issue much longer
119          return DocumentReference.from_string(value)
120
121        contents = FaunaDecoder._decode(value)
122
123        if "id" in contents and "coll" in contents and "ts" in contents:
124          doc_id = contents.pop("id")
125          doc_coll = contents.pop("coll")
126          doc_ts = contents.pop("ts")
127
128          return Document(
129              id=doc_id,
130              coll=doc_coll,
131              ts=doc_ts,
132              data=contents,
133          )
134        elif "name" in contents and "coll" in contents and "ts" in contents:
135          doc_name = contents.pop("name")
136          doc_coll = contents.pop("coll")
137          doc_ts = contents.pop("ts")
138
139          return NamedDocument(
140              name=doc_name,
141              coll=doc_coll,
142              ts=doc_ts,
143              data=contents,
144          )
145        else:
146          # Unsupported document reference. Return the unwrapped value to futureproof.
147          return contents
148
149      if "@ref" in dct:
150        value = dct["@ref"]
151        if "id" not in value and "name" not in value:
152          # Unsupported document reference. Return the unwrapped value to futureproof.
153          return value
154
155        col = FaunaDecoder._decode(value["coll"])
156        doc_ref: Union[DocumentReference, NamedDocumentReference]
157
158        if "id" in value:
159          doc_ref = DocumentReference(col, value["id"])
160        else:
161          doc_ref = NamedDocumentReference(col, value["name"])
162
163        if "exists" in value and not value["exists"]:
164          cause = value["cause"] if "cause" in value else None
165          return NullDocument(doc_ref, cause)
166
167        return doc_ref
168
169      if "@set" in dct:
170        value = dct["@set"]
171        if isinstance(value, str):
172          return Page(after=value)
173
174        after = value["after"] if "after" in value else None
175        data = FaunaDecoder._decode(value["data"]) if "data" in value else None
176
177        return Page(data=data, after=after)
178
179      if "@stream" in dct:
180        return StreamToken(dct["@stream"])
181
182    return {k: FaunaDecoder._decode(v) for k, v in dct.items()}

Supports the following types:

+--------------------+---------------+ | Python | Fauna | +====================+===============+ | dict | object | +--------------------+---------------+ | list, tuple | array | +--------------------+---------------+ | str | string | +--------------------+---------------+ | int | @int | +--------------------+---------------+ | int | @long | +--------------------+---------------+ | float | @double | +--------------------+---------------+ | datetime.datetime | @time | +--------------------+---------------+ | datetime.date | @date | +--------------------+---------------+ | True | true | +--------------------+---------------+ | False | false | +--------------------+---------------+ | None | null | +--------------------+---------------+ | bytearray | @bytes | +--------------------+---------------+ | *DocumentReference | @ref | +--------------------+---------------+ | *Document | @doc | +--------------------+---------------+ | Module | @mod | +--------------------+---------------+ | Page | @set | +--------------------+---------------+ | StreamToken | @stream | +--------------------+---------------+

@staticmethod
def decode(obj: Any):
54  @staticmethod
55  def decode(obj: Any):
56    """Decodes supported objects from the tagged typed into untagged.
57
58        Examples:
59            - { "@int": "100" } decodes to 100 of type int
60            - { "@double": "100" } decodes to 100.0 of type float
61            - { "@long": "100" } decodes to 100 of type int
62            - { "@time": "..." } decodes to a datetime
63            - { "@date": "..." } decodes to a date
64            - { "@doc": ... } decodes to a Document or NamedDocument
65            - { "@ref": ... } decodes to a DocumentReference or NamedDocumentReference
66            - { "@mod": ... } decodes to a Module
67            - { "@set": ... } decodes to a Page
68            - { "@stream": ... } decodes to a StreamToken
69            - { "@bytes": ... } decodes to a bytearray
70
71        :param obj: the object to decode
72        """
73    return FaunaDecoder._decode(obj)

Decodes supported objects from the tagged typed into untagged.

Examples: - { "@int": "100" } decodes to 100 of type int - { "@double": "100" } decodes to 100.0 of type float - { "@long": "100" } decodes to 100 of type int - { "@time": "..." } decodes to a datetime - { "@date": "..." } decodes to a date - { "@doc": ... } decodes to a Document or NamedDocument - { "@ref": ... } decodes to a DocumentReference or NamedDocumentReference - { "@mod": ... } decodes to a Module - { "@set": ... } decodes to a Page - { "@stream": ... } decodes to a StreamToken - { "@bytes": ... } decodes to a bytearray

Parameters
  • obj: the object to decode