Basics#

Here we will try to describe our first graph. To begin with we will need to setup an environment:

$ pip install hiku

Simplest one-field graph#

Note

Source code of this example can be found on GitHub.

Let’s define graph with only one field, which is easy to compute, for example it would be a current time:

from datetime import datetime

from hiku.graph import Graph, Root, Field

GRAPH = Graph([
    Root([
        Field('now', None, lambda _: [datetime.now().isoformat()]),
    ]),
])

This is the simplest Graph with one Field in the Root node.

Note

We are using lambda-function and ignoring it’s first argument because this function is used to load only one field and this field in the Root node. In other cases you will need to use this and possibly other required arguments.

Then this field could be queried using this query:

{ now }

To perform this query let’s define a helper function execute:

from hiku.engine import Engine
from hiku.result import denormalize
from hiku.executors.sync import SyncExecutor
from hiku.readers.graphql import read

hiku_engine = Engine(SyncExecutor())


def execute(graph, query_string):
    query = read(query_string)
    result = hiku_engine.execute(graph, query)
    return denormalize(graph, result)

Then we will be ready to execute our query:

result = execute(GRAPH, '[:now]')
assert result == {'now': '2015-10-21T07:28:00'}

You can also test this graph using special web console application, this is how to setup and run it:

from hiku.console.ui import ConsoleApplication

app = ConsoleApplication(GRAPH, hiku_engine, debug=True)

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    http_server = make_server('localhost', 5000, app)
    http_server.serve_forever()

Then just open http://localhost:5000/ url in your browser and perform query from the console.

Linking node to node#

Note

Source code of this example can be found on GitHub.

Let’s extend our data with one more entity - Actor:

data = {
    'Character': {
        1: dict(id=1, name='James T. Kirk', species='Human'),
        2: dict(id=2, name='Spock', species='Vulcan/Human'),
        3: dict(id=3, name='Leonard McCoy', species='Human'),
    },
    'Actor': {
        1: dict(id=1, character_id=1, name='William Shatner'),
        2: dict(id=2, character_id=2, name='Leonard Nimoy'),
        3: dict(id=3, character_id=3, name='DeForest Kelley'),
        4: dict(id=4, character_id=1, name='Chris Pine'),
        5: dict(id=5, character_id=2, name='Zachary Quinto'),
        6: dict(id=6, character_id=3, name='Karl Urban'),
    },
}

Where actor will have a reference to the played character – character_id. We will also need id fields in both nodes in order to link them with each other.

Here is our extended graph definition:

 1from collections import defaultdict
 2
 3from hiku.graph import Graph, Root, Field, Node, Link
 4from hiku.types import TypeRef, Sequence
 5
 6def character_data(fields, ids):
 7    result = []
 8    for id_ in ids:
 9        character = data['Character'][id_]
10        result.append([character[field.name] for field in fields])
11    return result
12
13def actor_data(fields, ids):
14    result = []
15    for id_ in ids:
16        actor = data['Actor'][id_]
17        result.append([actor[field.name] for field in fields])
18    return result
19
20def character_to_actors_link(ids):
21    mapping = defaultdict(list)
22    for row in data['Actor'].values():
23        mapping[row['character_id']].append(row['id'])
24    return [mapping[id_] for id_ in ids]
25
26def actor_to_character_link(ids):
27    mapping = {}
28    for row in data['Actor'].values():
29        mapping[row['id']] = row['character_id']
30    return [mapping[id_] for id_ in ids]
31
32def to_characters_link():
33    return [1, 2, 3]
34
35GRAPH = Graph([
36    Node('Character', [
37        Field('id', None, character_data),
38        Field('name', None, character_data),
39        Field('species', None, character_data),
40        Link('actors', Sequence[TypeRef['Actor']],
41             character_to_actors_link, requires='id'),
42    ]),
43    Node('Actor', [
44        Field('id', None, actor_data),
45        Field('name', None, actor_data),
46        Link('character', TypeRef['Character'],
47             actor_to_character_link, requires='id'),
48    ]),
49    Root([
50        Link('characters', Sequence[TypeRef['Character']],
51             to_characters_link, requires=None),
52    ]),
53])

Here actors Link [40-41], defined in the Character node [36], requires='id' field [37] to map characters to actors. That’s why id field [37] was added to the Character node [36]. The same work should be done in the Actor node [43] to implement backward character link [46-47].

requires argument can be specified as a list of fields, in this case Hiku will resolve all of them and pass a list of dict to resolver.

character_to_actors_link function [20] accepts ids of the characters and should return list of lists – ids of the actors, in the same order, so every character id can be associated with a list of actor ids. This is how one to many links works.

actor_to_character_link function [26] requires/accepts ids of the actors and returns ids of the characters in the same order. This is how many to one links works.

So now we can include linked node fields in our query:

result = execute(GRAPH, "{ characters { name actors { name } } }")
assert result == {
    'characters': [
        {'name': 'James T. Kirk',
         'actors': [{'name': 'William Shatner'},
                    {'name': 'Chris Pine'}]},
        {'name': 'Spock',
         'actors': [{'name': 'Leonard Nimoy'},
                    {'name': 'Zachary Quinto'}]},
        {'name': 'Leonard McCoy',
         'actors': [{'name': 'DeForest Kelley'},
                    {'name': 'Karl Urban'}]},
    ],
}

We can go further and follow character link from the Actor node and return fields from Character node. This is an example of the cyclic links, which is normal when this feature is desired, as long as query is a hierarchical finite structure and result follows it’s structure.

 1result = execute(GRAPH, "{ characters { name actors { name character { name } } } }")
 2assert result == {
 3    'characters': [
 4        {'name': 'James T. Kirk',
 5         'actors': [{'name': 'William Shatner',
 6                     'character': {'name': 'James T. Kirk'}},
 7                    {'name': 'Chris Pine',
 8                     'character': {'name': 'James T. Kirk'}}]},
 9        {'name': 'Spock',
10         'actors': [{'name': 'Leonard Nimoy',
11                     'character': {'name': 'Spock'}},
12                    {'name': 'Zachary Quinto',
13                     'character': {'name': 'Spock'}}]},
14        {'name': 'Leonard McCoy',
15         'actors': [{'name': 'DeForest Kelley',
16                     'character': {'name': 'Leonard McCoy'}},
17                    {'name': 'Karl Urban',
18                     'character': {'name': 'Leonard McCoy'}}]},
19    ],
20}

As you can see, there are duplicate entries in the result [11,13,15] – this is how our cycle can be seen, the same character Spock seen multiple times.