Skip to content

Custom Scalars input serialization in variables #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/modules/gql.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Sub-Packages
client
transport
dsl
utilities
6 changes: 6 additions & 0 deletions docs/modules/utilities.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
gql.utilities
=============

.. currentmodule:: gql.utilities

.. automodule:: gql.utilities
134 changes: 134 additions & 0 deletions docs/usage/custom_scalars.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
Custom Scalars
==============

Scalar types represent primitive values at the leaves of a query.

GraphQL provides a number of built-in scalars (Int, Float, String, Boolean and ID), but a GraphQL backend
can add additional custom scalars to its schema to better express values in their data model.

For example, a schema can define the Datetime scalar to represent an ISO-8601 encoded date.

The schema will then only contain:

.. code-block:: python

scalar Datetime

When custom scalars are sent to the backend (as inputs) or from the backend (as outputs),
their values need to be serialized to be composed
of only built-in scalars, then at the destination the serialized values will be parsed again to
be able to represent the scalar in its local internal representation.

Because this serialization/unserialization is dependent on the language used at both sides, it is not
described in the schema and needs to be defined independently at both sides (client, backend).

A custom scalar value can have two different representations during its transport:

- as a serialized value (usually as json):

* in the results sent by the backend
* in the variables sent by the client alongside the query

- as "literal" inside the query itself sent by the client

To define a custom scalar, you need 3 methods:

- a :code:`serialize` method used:

* by the backend to serialize a custom scalar output in the result
* by the client to serialize a custom scalar input in the variables

- a :code:`parse_value` method used:

* by the backend to unserialize custom scalars inputs in the variables sent by the client
* by the client to unserialize custom scalars outputs from the results

- a :code:`parse_literal` method used:

* by the backend to unserialize custom scalars inputs inside the query itself

To define a custom scalar object, we define a :code:`GraphQLScalarType` from graphql-core with
its name and the implementation of the above methods.

Example for Datetime:

.. code-block:: python

from datetime import datetime
from typing import Any, Dict, Optional

from graphql import GraphQLScalarType, ValueNode
from graphql.utilities import value_from_ast_untyped


def serialize_datetime(value: Any) -> str:
return value.isoformat()


def parse_datetime_value(value: Any) -> datetime:
return datetime.fromisoformat(value)


def parse_datetime_literal(
value_node: ValueNode, variables: Optional[Dict[str, Any]] = None
) -> datetime:
ast_value = value_from_ast_untyped(value_node, variables)
return parse_datetime_value(ast_value)


DatetimeScalar = GraphQLScalarType(
name="Datetime",
serialize=serialize_datetime,
parse_value=parse_datetime_value,
parse_literal=parse_datetime_literal,
)

Custom Scalars in inputs
------------------------

To provide custom scalars in input with gql, you can:

- serialize the scalar yourself as "literal" in the query:

.. code-block:: python

query = gql(
"""{
shiftDays(time: "2021-11-12T11:58:13.461161", days: 5)
}"""
)

- serialize the scalar yourself in a variable:

.. code-block:: python

query = gql("query shift5days($time: Datetime) {shiftDays(time: $time, days: 5)}")

variable_values = {
"time": "2021-11-12T11:58:13.461161",
}

result = client.execute(query, variable_values=variable_values)

- add a custom scalar to the schema with :func:`update_schema_scalars <gql.utilities.update_schema_scalars>`
and execute the query with :code:`serialize_variables=True`
and gql will serialize the variable values from a Python object representation.

For this, you need to provide a schema or set :code:`fetch_schema_from_transport=True`
in the client to request the schema from the backend.

.. code-block:: python

from gql.utilities import update_schema_scalars

async with Client(transport=transport, fetch_schema_from_transport=True) as session:

update_schema_scalars(session.client.schema, [DatetimeScalar])

query = gql("query shift5days($time: Datetime) {shiftDays(time: $time, days: 5)}")

variable_values = {"time": datetime.now()}

result = await session.execute(
query, variable_values=variable_values, serialize_variables=True
)
1 change: 1 addition & 0 deletions docs/usage/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Usage
variables
headers
file_upload
custom_scalars
Loading