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.