Skip to content

Commit

Permalink
refactor scalars, refactor field type info and link type info
Browse files Browse the repository at this point in the history
  • Loading branch information
m.kindritskiy committed Aug 5, 2023
1 parent 3072056 commit b07be89
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 375 deletions.
1 change: 1 addition & 0 deletions docs/changelog/changes_07.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Changes in 0.7
- Added support for unions :ref:`Check unions documentation <unions-doc>`
- Added support for interfaces :ref:`Check interfaces documentation <interfaces-doc>`
- Added support for enums :ref:`Check enums documentation <enums-doc>`
- Added support for custom scalars :ref:`Check custom scalars documentation <scalars-doc>`

Backward-incompatible changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions docs/enums.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ In `hiku` you can define enum type like this:
def user_fields_resolver(fields, ids):
def get_field(field, user):
if field.name == 'id':
return user.id
return user['id']
elif field.name == 'status':
return user.status
return user['status']
return [[get_field(field, users[id]) for field in fields] for id in ids]
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ User's Guide
asyncio
graphql
protobuf
scalars
enums
interfaces
unions
Expand Down
273 changes: 51 additions & 222 deletions docs/scalars.rst
Original file line number Diff line number Diff line change
@@ -1,79 +1,91 @@
Enums
Scalars
=====

.. _enums-doc:
.. _scalars-doc:

Enums are a special types that can be used to define a set of possible values for a field.
Scalar are a types that used to represent leaves of a graph.

In graphql you can use enum type like this:
Hiku has a few built-in scalars according to the GraphQL specification:

.. code-block::
- ``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.

enum Status {
ACTIVE
DELETED
}
Although these scalar types is sufficient to represent the majority of data types returned from graph,
it is sometimes necessary to define custom scalar types.

type Usr {
id: ID!
status: Status!
}
``Hiku`` has a few additional custom scalars:

type Query {
user: User!
}
- ``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.

Enum from string
----------------
Custom scalars
--------------

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

.. code-block:: python
from datetime import datetime
from hiku.graph import Field, Graph, Link, Node, Root
from hiku.enum import Enum
from hiku.types import ID, TypeRef, Optional, EnumRef
from hiku.scalar import Scalar
from hiku.types import ID, TypeRef, Optional
users = {
1: {'id': "1", 'status': 'ACTIVE'},
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):
return datetime.strptime(value, '%Y-%m-%d').date()
@classmethod
def serialize(cls, value):
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 == 'status':
return user.status
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):
return 1
enums = [
Enum('Status', ['ACTIVE', 'DELETED'])
]
scalars = [YMDDate]
GRAPH = Graph([
Node('User', [
Field('id', ID, user_fields_resolver),
Field('status', EnumRef['Status'], user_fields_resolver),
Field('dateCreated', YMDDate, user_fields_resolver),
]),
Root([
Link('user', TypeRef['User'], get_user, requires=None),
]),
], enums=enums)
], scalars=scalars)
Lets look at the example above:

- ``Enum`` type is defined with a name and a list of possible values.
- ``User.status`` field has type ``EnumRef['Status']`` which is a reference to the ``Status`` enum type.
- ``status`` field returns ``user.status`` which is plain string.

.. note::

You can not return a value that is not in the enum list of possible values. Hiku will raise an error if you try to do so.
- ``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-%`` formated string.

Now lets look at the query:

Expand All @@ -82,7 +94,7 @@ Now lets look at the query:
query {
user {
id
status
dateCreated
}
}
Expand All @@ -92,188 +104,5 @@ The result will be:
{
'id': "1",
'status': 'ACTIVE',
}
Enum from builtin Enum type
----------------------------------

You can also use python builtin ``Enum`` type to define an enum type in ``hiku``:

.. code-block:: python
from enum import Enum as PyEnum
from hiku.enum import Enum
class Status(PyEnum):
ACTIVE = 'active'
DELETED = 'deleted'
Graph(..., enums=[Enum.from_builtin(Status)])
``Enum.from_builtin`` will create ``hiku.enum.EnumFromBuiltin``:

- ``EnumFromBuiltin`` will use ``Enum.__name__`` as a enum name.
- ``EnumFromBuiltin`` will use ``Enum.__members__`` to get a list of possible values.
- ``EnumFromBuiltin`` will use ``member.name`` to get a value name:

.. code-block:: python
class Status(PyEnum):
ACTIVE = 1
DELETED = 2
is equivalent to:

.. code-block:: python
enum Status { ACTIVE, DELETED }
If you use builtin python ``Enum``, then you MUST return enum value from the resolver function, otherwise ``hiku`` will raise an error.

.. code-block:: python
def user_fields_resolver(fields, ids):
def get_field(field, user):
if field.name == 'id':
return user.id
elif field.name == 'status':
return Status(user.status)
return [[get_field(field, users[id]) for field in fields] for id in ids]
By default ``Enum.from_builtin`` will use ``Enum.__name__`` as a name for the enum type.

.. note::

You can create enum using ``Enum`` class directly if you want custom name (for example non-pep8 compliant):

.. code-block:: python
Status = Enum('User_Status', ['ACTIVE', 'DELETED'])
If you want to specify different name you can pass ``name`` argument to ``Enum.from_builtin`` method.

.. code-block:: python
Graph(..., enums=[Enum.from_builtin(Status, name='User_Status')])
Custom Enum type
----------------

You can also create custom enum type by subclassing ``hiku.enum.BaseEnum`` class:

.. code-block:: python
from hiku.enum import BaseEnum
class IntToStrEnum(BaseEnum):
_MAPPING = {1: 'one', 2: 'two', 3: 'three'}
_INVERTED_MAPPING = {v: k for k, v in _MAPPING.items()}
def __init__(self, name: str, values: list[int], description: str = None):
super().__init__(name, [_MAPPING[v] for v in values], description)
def parse(self, value: str) -> int:
return self._INVERTED_MAPPING[value]
def serialize(self, value: int) -> str:
return self._MAPPING[value]
Enum serialization
------------------

``Enum`` serializes values into strings. If value is not in the list of possible values, then ``hiku`` will raise an error.

``EnumFromBuiltin`` serializes values which are instances of ``Enum`` class into strings by calling `.name` on enum value. If value is not an instance of ``Enum`` class, then ``hiku`` will raise an error.

You can also define custom serialization for your enum type by subclassing ``hiku.enum.BaseEnum`` class.

Enum parsing
------------

``Enum`` parses values into strings. If value is not in the list of possible values, then ``hiku`` will raise an error.

``EnumFromBuiltin`` parses values into enum values by calling ``Enum(value)``. If value is not in the list of possible values, then ``hiku`` will raise an error.

You can also define custom parsing for your enum type by subclassing ``hiku.enum.BaseEnum`` class.

Enum as a field argument
------------------------

You can use enum as a field argument:

.. code-block:: python
import enum
from hiku.enum import Enum
from hiku.graph import Node, Root, Field, Link, Graph, Option
from hiku.types import ID, TypeRef, Optional, EnumRef
users = [
{'id': "1", 'status': Status.ACTIVE},
{'id': "2", 'status': Status.DELETED},
]
def link_users(opts):
ids = []
for user in users:
# here opts['status'] will be an instance of Status enum
if user['status'] == opts['status']:
ids.append(user.id)
return ids
class Status(enum.Enum):
ACTIVE = 'active'
DELETED = 'deleted'
GRAPH = Graph([
Node('User', [
Field('id', ID, user_fields_resolver),
Field('status', EnumRef['Status'], user_fields_resolver),
]),
Root([
Link(
'users',
Sequence[TypeRef['User']],
link_users,
requires=None,
options=[
Option('status', EnumRef['Status'], default=Status.ACTIVE),
]
),
]),
], enums=[Enum.from_builtin(Status)])
Now you can use enum as a field argument:

.. code-block::
query {
users(status: DELETED) {
id
status
}
'dateCreated': '2023-06-15',
}
The result will be:

.. code-block::
[{
"id": "2",
"status": "DELETED",
}]
.. note::

Input value will be parsed using ``.parse`` method of ``Enum`` type.

For ``Enum`` input value will be parsed into ``str``.

For ``EnumFromBuiltin`` input value will be parsed into python Enum instance.
Loading

0 comments on commit b07be89

Please sign in to comment.