|
| 1 | +Serialization |
| 2 | +------------- |
| 3 | + |
| 4 | +There are two serialization mechanisms employed by the driver: |
| 5 | + |
| 6 | +* JSON serialization/deserialization |
| 7 | +* Document serialization/deserialization |
| 8 | + |
| 9 | +All serializers must inherit from the :class:`arangoasync.serialization.Serializer` class. They must |
| 10 | +implement a :func:`arangoasync.serialization.Serializer.dumps` method can handle both |
| 11 | +single objects and sequences. |
| 12 | + |
| 13 | +Deserializers mush inherit from the :class:`arangoasync.serialization.Deserializer` class. These have |
| 14 | +two methods, :func:`arangoasync.serialization.Deserializer.loads` and :func:`arangoasync.serialization.Deserializer.loads_many`, |
| 15 | +which must handle loading of a single document and multiple documents, respectively. |
| 16 | + |
| 17 | +JSON |
| 18 | +==== |
| 19 | + |
| 20 | +Usually there's no need to implement your own JSON serializer/deserializer, but such an |
| 21 | +implementation could look like the following. |
| 22 | + |
| 23 | +**Example:** |
| 24 | + |
| 25 | +.. code-block:: python |
| 26 | +
|
| 27 | + import json |
| 28 | + from typing import Sequence, cast |
| 29 | + from arangoasync.collection import StandardCollection |
| 30 | + from arangoasync.database import StandardDatabase |
| 31 | + from arangoasync.exceptions import DeserializationError, SerializationError |
| 32 | + from arangoasync.serialization import Serializer, Deserializer |
| 33 | + from arangoasync.typings import Json, Jsons |
| 34 | +
|
| 35 | +
|
| 36 | + class CustomJsonSerializer(Serializer[Json]): |
| 37 | + def dumps(self, data: Json | Sequence[str | Json]) -> str: |
| 38 | + try: |
| 39 | + return json.dumps(data, separators=(",", ":")) |
| 40 | + except Exception as e: |
| 41 | + raise SerializationError("Failed to serialize data to JSON.") from e |
| 42 | +
|
| 43 | +
|
| 44 | + class CustomJsonDeserializer(Deserializer[Json, Jsons]): |
| 45 | + def loads(self, data: bytes) -> Json: |
| 46 | + try: |
| 47 | + return json.loads(data) # type: ignore[no-any-return] |
| 48 | + except Exception as e: |
| 49 | + raise DeserializationError("Failed to deserialize data from JSON.") from e |
| 50 | +
|
| 51 | + def loads_many(self, data: bytes) -> Jsons: |
| 52 | + return self.loads(data) # type: ignore[return-value] |
| 53 | +
|
| 54 | +You would then use the custom serializer/deserializer when creating a client: |
| 55 | + |
| 56 | +.. code-block:: python |
| 57 | +
|
| 58 | + from arangoasync import ArangoClient |
| 59 | + from arangoasync.auth import Auth |
| 60 | +
|
| 61 | + # Initialize the client for ArangoDB. |
| 62 | + async with ArangoClient( |
| 63 | + hosts="http://localhost:8529", |
| 64 | + serializer=CustomJsonSerializer(), |
| 65 | + deserializer=CustomJsonDeserializer(), |
| 66 | + ) as client: |
| 67 | + auth = Auth(username="root", password="passwd") |
| 68 | +
|
| 69 | + # Connect to "test" database as root user. |
| 70 | + test = await client.db("test", auth=auth) |
| 71 | +
|
| 72 | +Documents |
| 73 | +========= |
| 74 | + |
| 75 | +By default, the JSON serializer/deserializer is used for documents too, but you can provide your own |
| 76 | +document serializer and deserializer for fine-grained control over the format of a collection. Say |
| 77 | +that you are modeling your students data using Pydantic_. You want to be able to insert documents |
| 78 | +of a certain type, and also be able to read them back. More so, you would like to get multiple documents |
| 79 | +back using one of the formats provided by pandas_. |
| 80 | + |
| 81 | +**Example:** |
| 82 | + |
| 83 | +.. code-block:: python |
| 84 | +
|
| 85 | + import json |
| 86 | + import pandas as pd |
| 87 | + import pydantic |
| 88 | + import pydantic_core |
| 89 | + from typing import Sequence, cast |
| 90 | + from arangoasync import ArangoClient |
| 91 | + from arangoasync.auth import Auth |
| 92 | + from arangoasync.collection import StandardCollection |
| 93 | + from arangoasync.database import StandardDatabase |
| 94 | + from arangoasync.exceptions import DeserializationError, SerializationError |
| 95 | + from arangoasync.serialization import Serializer, Deserializer |
| 96 | + from arangoasync.typings import Json, Jsons |
| 97 | +
|
| 98 | +
|
| 99 | + class Student(pydantic.BaseModel): |
| 100 | + name: str |
| 101 | + age: int |
| 102 | +
|
| 103 | +
|
| 104 | + class StudentSerializer(Serializer[Student]): |
| 105 | + def dumps(self, data: Student | Sequence[Student | str]) -> str: |
| 106 | + try: |
| 107 | + if isinstance(data, Student): |
| 108 | + return data.model_dump_json() |
| 109 | + else: |
| 110 | + # You are required to support both str and Student types. |
| 111 | + serialized_data = [] |
| 112 | + for student in data: |
| 113 | + if isinstance(student, str): |
| 114 | + serialized_data.append(student) |
| 115 | + else: |
| 116 | + serialized_data.append(student.model_dump()) |
| 117 | + return json.dumps(serialized_data, separators=(",", ":")) |
| 118 | + except Exception as e: |
| 119 | + raise SerializationError("Failed to serialize data.") from e |
| 120 | +
|
| 121 | +
|
| 122 | + class StudentDeserializer(Deserializer[Student, pd.DataFrame]): |
| 123 | + def loads(self, data: bytes) -> Student: |
| 124 | + # Load a single document. |
| 125 | + try: |
| 126 | + return Student.model_validate(pydantic_core.from_json(data)) |
| 127 | + except Exception as e: |
| 128 | + raise DeserializationError("Failed to deserialize data.") from e |
| 129 | +
|
| 130 | + def loads_many(self, data: bytes) -> pd.DataFrame: |
| 131 | + # Load multiple documents. |
| 132 | + return pd.DataFrame(json.loads(data)) |
| 133 | +
|
| 134 | +You would then use the custom serializer/deserializer when working with collections: |
| 135 | + |
| 136 | +**Example:** |
| 137 | + |
| 138 | +.. code-block:: python |
| 139 | +
|
| 140 | + async def main(): |
| 141 | + # Initialize the client for ArangoDB. |
| 142 | + async with ArangoClient( |
| 143 | + hosts="http://localhost:8529", |
| 144 | + serializer=CustomJsonSerializer(), |
| 145 | + deserializer=CustomJsonDeserializer(), |
| 146 | + ) as client: |
| 147 | + auth = Auth(username="root", password="passwd") |
| 148 | +
|
| 149 | + # Connect to "test" database as root user. |
| 150 | + db: StandardDatabase = await client.db("test", auth=auth, verify=True) |
| 151 | +
|
| 152 | + # Populate the "students" collection. |
| 153 | + col = cast( |
| 154 | + StandardCollection[Student, Student, pd.DataFrame], |
| 155 | + db.collection( |
| 156 | + "students", |
| 157 | + doc_serializer=StudentSerializer(), |
| 158 | + doc_deserializer=StudentDeserializer()), |
| 159 | + ) |
| 160 | +
|
| 161 | + # Insert one document. |
| 162 | + doc = cast(Json, await col.insert(Student(name="John Doe", age=20))) |
| 163 | +
|
| 164 | + # Insert multiple documents. |
| 165 | + docs = cast(Jsons, await col.insert_many([ |
| 166 | + Student(name="Jane Doe", age=22), |
| 167 | + Student(name="Alice Smith", age=19), |
| 168 | + Student(name="Bob Johnson", age=21), |
| 169 | + ])) |
| 170 | +
|
| 171 | + # Get one document. |
| 172 | + john = await col.get(doc) |
| 173 | + assert type(john) == Student |
| 174 | +
|
| 175 | + # Get multiple documents. |
| 176 | + keys = [doc["_key"] for doc in docs] |
| 177 | + students = await col.get_many(keys) |
| 178 | + assert type(students) == pd.DataFrame |
| 179 | +
|
| 180 | +.. _Pydantic: https://docs.pydantic.dev/latest/ |
| 181 | +.. _pandas: https://pandas.pydata.org/ |
0 commit comments