Graphene: Generate an InputObjectType for testing

Created on 3 Oct 2017  路  4Comments  路  Source: graphql-python/graphene

Let's say I have an InputObjectType:

class MyIOT(graphene.InputObjectType):
    my_field = graphene.String()

Directly instantiating that as an instance container gives me a half implementation:

my_iot = MyIOT(my_field='asdf')

Yet, when it's constructed by graphene I'm able to access a full implementation, with methods like keys(). I've been hunting through the code for a couple hours, but can't seem to figure out where it's created. Any ideas?

Most helpful comment

Here's the answer (look at synthesize_input_container):

import graphene


def resolve_type_heirarchy(obj):
    if isinstance(obj, graphene.Field):
        nested_type = obj.type
        type_ = graphene.Field
    elif isinstance(obj, graphene.InputField):
        nested_type = obj.type
        type_ = graphene.InputField
    elif isinstance(obj, graphene.List):
        nested_type = obj.of_type
        type_ = graphene.List
    elif isinstance(obj, graphene.NonNull):
        nested_type = obj.of_type
        type_ = graphene.NonNull
    else:
        return (obj,)

    return (type_, *resolve_type_heirarchy(nested_type))


def resolve_leaf_type(obj):
    return resolve_type_heirarchy(obj)[-1]


def is_collection(obj):
    return graphene.List in resolve_type_heirarchy(obj)


def synthesize_input_container(input_object_type_cls, data):
    # pylint: disable=protected-access
    for field_name, value in data.items():
        field = input_object_type_cls._meta.fields[field_name]
        if is_collection(field):
            items = []
            for item in value:
                leaf_type = resolve_leaf_type(field)
                if issubclass(leaf_type, graphene.InputObjectType):
                    item = synthesize_input_container(leaf_type, item)
                items.append(item)
            data[field_name] = items
        else:
            if issubclass(field.type, graphene.InputObjectType):
                data[field_name] = synthesize_input_container(field.type, value)

    return input_object_type_cls._meta.container(data)

for use like:

def test_synthesize_input_container():
    class RelatedInput(graphene.InputObjectType):
        id = graphene.ID()

    class Input(graphene.InputObjectType):
        id = graphene.ID()
        ids = graphene.List(graphene.NonNull(graphene.ID))
        related = RelatedInput()
        relateds = graphene.List(graphene.NonNull(RelatedInput))

    input_raw = {
        'id': 'input_id_1',
        'ids': ['input_id_2',],
        'related': {'id': 'related_input_id_1'},
        'relateds': [{'id': 'related_input_id_2'}],
    }

    input_container = synthesize_input_container(Input, input_raw)

    assert isinstance(input_container, Input)
    assert input_container.id == input_raw['id']

    assert isinstance(input_container.ids, list)
    assert len(input_container.ids) == 1
    assert input_container.ids[0] == input_raw['ids'][0]

    assert isinstance(input_container.related, RelatedInput)
    assert input_container.related.id == input_raw['related']['id']

    assert isinstance(input_container.relateds, list)
    assert len(input_container.relateds) == 1
    assert isinstance(input_container.relateds[0], RelatedInput)
    assert input_container.relateds[0].id == input_raw['relateds'][0]['id']

All 4 comments

I'm a little bit closer. Getting the InputObjectType from the schema, and using create_container gives something a bit more useful (a fully structured InputObjectType):

MyIOT = schema.get_type('MyIOT')
MyIOT.create_container({'my_field': 'asdf'})

However, with a more complex example:

class ChildIOT(graphene.InputObjectType):
    nested_field = graphene.String()

    @property
    def invert_nested(self):
        return self.nested_field[::-1]

class ParentIOT(graphene.InputObjectType):
    root_field = graphene.String()
    child_field = graphene.Field(ChildIOT)

# ... create schema, etc.

FullParentIOT = schema.get_type('ParentIOT')  # is this actually called "mounted"?
fp_iot = FullParentIOT.create_container({
    'root_field': 'i am root',
    'child_field': {'nested_field': 'i am nested'},
})

assert type(fp_iot.child_field) == dict  # unfortunately, this is not a hydrated / full ChildIOT instance!
# fp_iot.child_field.invert_nested  # of course, we can't access ChildIOT methods on a dict instance.

So, how do I build a complex InputObjectType? Do I need to manually build the subcomponents and then inject them?

Here's the answer (look at synthesize_input_container):

import graphene


def resolve_type_heirarchy(obj):
    if isinstance(obj, graphene.Field):
        nested_type = obj.type
        type_ = graphene.Field
    elif isinstance(obj, graphene.InputField):
        nested_type = obj.type
        type_ = graphene.InputField
    elif isinstance(obj, graphene.List):
        nested_type = obj.of_type
        type_ = graphene.List
    elif isinstance(obj, graphene.NonNull):
        nested_type = obj.of_type
        type_ = graphene.NonNull
    else:
        return (obj,)

    return (type_, *resolve_type_heirarchy(nested_type))


def resolve_leaf_type(obj):
    return resolve_type_heirarchy(obj)[-1]


def is_collection(obj):
    return graphene.List in resolve_type_heirarchy(obj)


def synthesize_input_container(input_object_type_cls, data):
    # pylint: disable=protected-access
    for field_name, value in data.items():
        field = input_object_type_cls._meta.fields[field_name]
        if is_collection(field):
            items = []
            for item in value:
                leaf_type = resolve_leaf_type(field)
                if issubclass(leaf_type, graphene.InputObjectType):
                    item = synthesize_input_container(leaf_type, item)
                items.append(item)
            data[field_name] = items
        else:
            if issubclass(field.type, graphene.InputObjectType):
                data[field_name] = synthesize_input_container(field.type, value)

    return input_object_type_cls._meta.container(data)

for use like:

def test_synthesize_input_container():
    class RelatedInput(graphene.InputObjectType):
        id = graphene.ID()

    class Input(graphene.InputObjectType):
        id = graphene.ID()
        ids = graphene.List(graphene.NonNull(graphene.ID))
        related = RelatedInput()
        relateds = graphene.List(graphene.NonNull(RelatedInput))

    input_raw = {
        'id': 'input_id_1',
        'ids': ['input_id_2',],
        'related': {'id': 'related_input_id_1'},
        'relateds': [{'id': 'related_input_id_2'}],
    }

    input_container = synthesize_input_container(Input, input_raw)

    assert isinstance(input_container, Input)
    assert input_container.id == input_raw['id']

    assert isinstance(input_container.ids, list)
    assert len(input_container.ids) == 1
    assert input_container.ids[0] == input_raw['ids'][0]

    assert isinstance(input_container.related, RelatedInput)
    assert input_container.related.id == input_raw['related']['id']

    assert isinstance(input_container.relateds, list)
    assert len(input_container.relateds) == 1
    assert isinstance(input_container.relateds[0], RelatedInput)
    assert input_container.relateds[0].id == input_raw['relateds'][0]['id']

Great implementation, thank you it really helped me!

@dfee, you managed to make this work when using a graphene DateTime scalar? I could not make it with your solution.

Was this page helpful?
0 / 5 - 0 ratings