Usage with EdgeDB¶
EdgeDB is an interesting new graph-relational database system. It includes a powerful and ergonomic query language “EdgeQL”, along with client libraries that integrate well with their respective language ecosystems.
In this example we demonstrate a few ways of integrating EdgeDB’s
Python client library
with msgspec.
Setup¶
This is not intended to be a complete EdgeDB tutorial; for that we recommend going through the official EdgeDB quickstart. This example assumes you already have the EdgeDB CLI and Python library installed.
After cloning the msgspec repo, navigate to the edgedb example
directory here. Then
initialize a new edgedb project.
$ edgedb project init --server-instance edgedb-msgspec-example --non-interactive
This will setup a new instance and apply the example schema:
module default {
type Person {
required name: str;
}
type Movie {
required title: str;
multi actors: Person;
}
};
We then need to insert some records. This is done with the following EdgeQL query:
INSERT Movie {
title := "Dune",
actors := {
(INSERT Person { name := "Timothée Chalamet" }),
(INSERT Person { name := "Zendaya" })
}
};
To run this, execute the following:
$ edgedb query -f insert_data.edgeql
JSON Encoding Query Results¶
The EdgeDB Python library returns objects as edgedb.Object instances
(docs).
Here we query the movie “Dune” that we inserted above, requesting the movie
title and actors’ names.
>>> import edgedb
>>> import msgspec
>>> client = edgedb.create_client()
>>> dune = client.query_single(
... """
... SELECT Movie {
... title,
... actors: {
... name
... }
... }
... FILTER .title = 'Dune'
... LIMIT 1
... """
... )
>>> dune
Object{title := 'Dune', actors := [Object{name := 'Timothée Chalamet'}, Object{name := 'Zendaya'}]}
>>> type(dune)
edgedb.Object
These edgedb.Object instances are duck-type compatible with dataclasses,
which means msgspec already knows how to JSON encode them.
>>> json = msgspec.json.encode(dune)
>>> print(msgspec.json.format(json.decode())) # pretty-print the JSON
{
"id": "b21913c4-3b68-11ee-89b0-2f0b6819503d",
"title": "Dune",
"actors": [
{
"id": "b219195a-3b68-11ee-89b0-5b3794805cc7",
"name": "Timothée Chalamet"
},
{
"id": "b2192058-3b68-11ee-89b0-f7d83b95fb13",
"name": "Zendaya"
}
]
}
Note that if you’re immediately JSON encoding the results you may be better
served by using EdgeDB’s query_json/query_single_json methods, which
return JSON strings directly (but strip the id fields).
>>> edgedb_json = client.query_single_json(
... """
... SELECT Movie {
... title,
... actors: {
... name
... }
... }
... FILTER .title = 'Dune'
... LIMIT 1
... """
... )
>>> edgedb_json
'{"title" : "Dune", "actors" : [{"name" : "Timothée Chalamet"},{"name" : "Zendaya"}]}'
If needed, this JSON string may be efficiently composed into a larger JSON
object using msgspec.Raw. Here we add some additional outer structure
wrapping the query result:
>>> import datetime
>>> msg = {
... "timestamp": datetime.datetime.now(datetime.timezone.utc),
... "server_version": "3.2",
... "query_result": msgspec.Raw(edgedb_json),
... }
>>> json = msgspec.json.encode(msg)
>>> print(msgspec.json.format(json.decode())) # pretty-print the JSON
{
"timestamp": "2023-08-15T14:37:12.733731Z",
"server_version": "3.2",
"query_result": {
"title": "Dune",
"actors": [
{
"name": "Timothée Chalamet"
},
{
"name": "Zendaya"
}
]
}
}
Supporting Other EdgeDB Types¶
Besides edgedb.Object, msgspec also includes builtin support for JSON
encoding edgedb.NamedTuple types. There are a few remaining edgedb
types that msgspec doesn’t support out-of-the-box:
JSON encoding support for these may be added through the use of extensions.
>>> def enc_hook(obj):
... if isinstance(obj, (edgedb.DateDuration, edgedb.RelativeDuration)):
... # The str representation of these types are ISO8601 durations,
... return str(obj)
... # Raise a NotImplementedError for unsupported types
... raise NotImplementedError
>>> duration = client.query_single('SELECT <cal::date_duration>"1 year 2 days"')
>>> duration
<edgedb.DateDuration "P1Y2D">
>>> msgspec.json.encode(duration, enc_hook=enc_hook)
b'"P1Y2D"'
Converting Results to Structs¶
If your application contains complex server-side logic, you may wish to convert
the query results into some other application-specific structured type.
msgspec supports automatic conversion to other types msgspec.convert.
Here we’ll define two msgspec.Struct types mirroring our Schema above:
>>> class Person(msgspec.Struct):
... name: str
>>> class Movie(msgspec.Struct):
... title: str
... actors: list[Person]
We can then convert the edgedb.Object results into our Struct types
using msgspec.convert. Note that the same conversion process would work if
Person or Movie were defined as dataclasses or attrs types instead.
>>> msgspec.convert(dune, Movie, from_attributes=True)
Movie(title='Dune', actors=[Person(name='Timothée Chalamet'), Person(name='Zendaya')])
These structs may then be used to implement application logic (mutating/combining them as needed) before serializing the output to JSON.