|
| 1 | +Custom Scalars |
| 2 | +============== |
| 3 | + |
| 4 | +Scalar types represent primitive values at the leaves of a query. |
| 5 | + |
| 6 | +GraphQL provides a number of built-in scalars (Int, Float, String, Boolean and ID), but a GraphQL backend |
| 7 | +can add additional custom scalars to its schema to better express values in their data model. |
| 8 | + |
| 9 | +For example, a schema can define the Datetime scalar to represent an ISO-8601 encoded date. |
| 10 | + |
| 11 | +The schema will then only contain: |
| 12 | + |
| 13 | +.. code-block:: python |
| 14 | +
|
| 15 | + scalar Datetime |
| 16 | +
|
| 17 | +When custom scalars are sent to the backend (as inputs) or from the backend (as outputs), |
| 18 | +their values need to be serialized to be composed |
| 19 | +of only built-in scalars, then at the destination the serialized values will be parsed again to |
| 20 | +be able to represent the scalar in its local internal representation. |
| 21 | + |
| 22 | +Because this serialization/unserialization is dependent on the language used at both sides, it is not |
| 23 | +described in the schema and needs to be defined independently at both sides (client, backend). |
| 24 | + |
| 25 | +A custom scalar value can have two different representations during its transport: |
| 26 | + |
| 27 | + - as a serialized value (usually as json): |
| 28 | + |
| 29 | + * in the results sent by the backend |
| 30 | + * in the variables sent by the client alongside the query |
| 31 | + |
| 32 | + - as "literal" inside the query itself sent by the client |
| 33 | + |
| 34 | +To define a custom scalar, you need 3 methods: |
| 35 | + |
| 36 | + - a :code:`serialize` method used: |
| 37 | + |
| 38 | + * by the backend to serialize a custom scalar output in the result |
| 39 | + * by the client to serialize a custom scalar input in the variables |
| 40 | + |
| 41 | + - a :code:`parse_value` method used: |
| 42 | + |
| 43 | + * by the backend to unserialize custom scalars inputs in the variables sent by the client |
| 44 | + * by the client to unserialize custom scalars outputs from the results |
| 45 | + |
| 46 | + - a :code:`parse_literal` method used: |
| 47 | + |
| 48 | + * by the backend to unserialize custom scalars inputs inside the query itself |
| 49 | + |
| 50 | +To define a custom scalar object, we define a :code:`GraphQLScalarType` from graphql-core with |
| 51 | +its name and the implementation of the above methods. |
| 52 | + |
| 53 | +Example for Datetime: |
| 54 | + |
| 55 | +.. code-block:: python |
| 56 | +
|
| 57 | + from datetime import datetime |
| 58 | + from typing import Any, Dict, Optional |
| 59 | +
|
| 60 | + from graphql import GraphQLScalarType, ValueNode |
| 61 | + from graphql.utilities import value_from_ast_untyped |
| 62 | +
|
| 63 | +
|
| 64 | + def serialize_datetime(value: Any) -> str: |
| 65 | + return value.isoformat() |
| 66 | +
|
| 67 | +
|
| 68 | + def parse_datetime_value(value: Any) -> datetime: |
| 69 | + return datetime.fromisoformat(value) |
| 70 | +
|
| 71 | +
|
| 72 | + def parse_datetime_literal( |
| 73 | + value_node: ValueNode, variables: Optional[Dict[str, Any]] = None |
| 74 | + ) -> datetime: |
| 75 | + ast_value = value_from_ast_untyped(value_node, variables) |
| 76 | + return parse_datetime_value(ast_value) |
| 77 | +
|
| 78 | +
|
| 79 | + DatetimeScalar = GraphQLScalarType( |
| 80 | + name="Datetime", |
| 81 | + serialize=serialize_datetime, |
| 82 | + parse_value=parse_datetime_value, |
| 83 | + parse_literal=parse_datetime_literal, |
| 84 | + ) |
| 85 | +
|
| 86 | +Custom Scalars in inputs |
| 87 | +------------------------ |
| 88 | + |
| 89 | +To provide custom scalars in input with gql, you can: |
| 90 | + |
| 91 | +- serialize the scalar yourself as "literal" in the query: |
| 92 | + |
| 93 | +.. code-block:: python |
| 94 | +
|
| 95 | + query = gql( |
| 96 | + """{ |
| 97 | + shiftDays(time: "2021-11-12T11:58:13.461161", days: 5) |
| 98 | + }""" |
| 99 | + ) |
| 100 | +
|
| 101 | +- serialize the scalar yourself in a variable: |
| 102 | + |
| 103 | +.. code-block:: python |
| 104 | +
|
| 105 | + query = gql("query shift5days($time: Datetime) {shiftDays(time: $time, days: 5)}") |
| 106 | +
|
| 107 | + variable_values = { |
| 108 | + "time": "2021-11-12T11:58:13.461161", |
| 109 | + } |
| 110 | +
|
| 111 | + result = client.execute(query, variable_values=variable_values) |
| 112 | +
|
| 113 | +- add a custom scalar to the schema with :func:`update_schema_scalars <gql.utilities.update_schema_scalars>` |
| 114 | + and execute the query with :code:`serialize_variables=True` |
| 115 | + and gql will serialize the variable values from a Python object representation. |
| 116 | + |
| 117 | +For this, you need to provide a schema or set :code:`fetch_schema_from_transport=True` |
| 118 | +in the client to request the schema from the backend. |
| 119 | + |
| 120 | +.. code-block:: python |
| 121 | +
|
| 122 | + from gql.utilities import update_schema_scalars |
| 123 | +
|
| 124 | + async with Client(transport=transport, fetch_schema_from_transport=True) as session: |
| 125 | +
|
| 126 | + update_schema_scalars(session.client.schema, [DatetimeScalar]) |
| 127 | +
|
| 128 | + query = gql("query shift5days($time: Datetime) {shiftDays(time: $time, days: 5)}") |
| 129 | +
|
| 130 | + variable_values = {"time": datetime.now()} |
| 131 | +
|
| 132 | + result = await session.execute( |
| 133 | + query, variable_values=variable_values, serialize_variables=True |
| 134 | + ) |
0 commit comments