Scalars

Scalar are a types that used to represent leaves of a graph.

Hiku has a few built-in scalars according to the GraphQL specification:

  • ID - a string that represents unique identifier.

  • Int - a signed 32‐bit integer.

  • Float - a signed double-precision floating-point value.

  • String - a UTF‐8 character sequence.

  • Boolean - a boolean value: true or false.

  • Any - any value.

Although these scalar types are sufficient to represent the majority of data types returned from graph, it is sometimes necessary to define custom scalar types.

Hiku has a few additional custom scalars:

  • Date - a date value in ISO 8601 format.

  • DateTime - a date and time value in ISO 8601 format.

  • UUID - a UUID value.

Note

Hiku built-in custom scalars are not enabled by default. You need to enable them explicitly, by adding each scalar that you need to the scalars argument of the Graph constructor.

Custom scalars

If builtin scalars do not cover your specific needs, you can define custom scalar type like this:

from hiku.scalar import Scalar

class YMDDate(Scalar):
    """Format datetime info %Y-%m-%d"""
    @classmethod
    def parse(cls, value: str) -> datetime:
        return datetime.strptime(value, '%Y-%m-%d')

    @classmethod
    def serialize(cls, value: datetime) -> str:
        return value.strftime('%Y-%m-%d')

Note

By default scalar name will be the name of a class.

If you want to specify a custom name for scalar or add description, you can use @scalar() decorator:

from hiku.scalar import Scalar, scalar

@scalar('YearMonthDayDate', 'Format datetime info %Y-%m-%d')
class YMDDate(Scalar):
    ...

Now, lets look at the full example:

from datetime import datetime

from hiku.graph import Field, Graph, Link, Node, Root
from hiku.scalar import Scalar
from hiku.types import ID, TypeRef, Optional

users = {
    1: {'id': "1", 'dateCreated': datetime(2023, 6, 15, 12, 30, 59, 0)},
}

class YMDDate(Scalar):
    """Format datetime info %Y-%m-%d"""
    @classmethod
    def parse(cls, value: str) -> datetime:
        return datetime.strptime(value, '%Y-%m-%d')

    @classmethod
    def serialize(cls, value: datetime) -> str:
        return value.strftime('%Y-%m-%d')

def user_fields_resolver(fields, ids):
    def get_field(field, user):
        if field.name == 'id':
            return user['id']
        elif field.name == 'dateCreated':
            return user['dateCreated']

    return [[get_field(field, users[id]) for field in fields] for id in ids]

def get_user(opts):
    if opts['olderThen'] <= datetime(2023, 6, 15):
        return 1

    return Nothing

scalars = [YMDDate]

GRAPH = Graph([
    Node('User', [
        Field('id', ID, user_fields_resolver),
        Field('dateCreated', YMDDate, user_fields_resolver),
    ]),
    Root([
        Link(
            'user',
            TypeRef['User'],
            get_user,
            requires=None,
            options=[
                Option('olderThen', Optional[YMDDate]),
            ]
        ),
    ]),
], scalars=scalars)

Lets decode the example above:

  • YMDDate type is subclassing Scalar` and implements ``parse and serialize methods.

  • User.dateCreated field has type YMDDate which is a custom scalar.

  • dateCreated field returns user.dateCreated which is a datetime instance.

  • YMDDate.parse method will be called to parse datetime instance into %Y-%m-% formatted string.

  • user field has input argument olderThen which has scalar type YMDDate. YMDDate.serialize method will be called to serialize input argument string into datetime instance.

If we run this query:

query {
    user(olderThen: "2023-06-15") {
        id
        dateCreated
    }
}

We will get this result:

{
    "id": "1",
    "dateCreated": "2023-06-15",
}