在运行时Graphene上创建动态架构

时间:2018-05-28 05:23:26

标签: python graphql graphene-python

我花了3天时间才找到在python石墨烯中创建动态模式的方法。 我能找到的唯一相关结果是以下链接: https://github.com/graphql-python/graphene/blob/master/graphene/types/dynamic.py 但是我找不到任何文件。

整个想法是创建一个动态架构。我想提供一个GraphQL兼容的API,使用户能够查询我的内容,即使代码中没有定义模型。换句话说,我想在飞行中创建模型。我不知道该怎么做。

如果能为此提供一个例子,那将是一个很大的好处。

更新:

我的项目是一个无头CMS,其功能是用户可以创建自己的内容类型,我想提供一个GraphQL界面,使一切更容易,更灵活。

以下是我在DB中的内容类型的示例: { "id": "author", "name": "Book Author", "desc": "", "options":[ { "id": "author_faname", "label": "Sample Sample", "type": "text", "required": true, "placeholder":"One Two Three Four" }, { "id": "author_enname", "label": "Sample label", "type": "text", "required": true, "placeholder":"Sample Placeholder" } ] }

以下是基于该内容类型在DB中存储的内容:

{ "id": "9rqgbrox10", "content_type": "author", "data":{ "author_fname":"Jimmy", "author_ename":"Hello" } }

现在因为我的模型没有在Code中声明并且它们完全在DB中,所以我想在运行中制作我的模式,我不知道什么是最好的解决方案。我知道应该有办法,因为其他无头CMS项目正在提供这个。

提前致谢!

2 个答案:

答案 0 :(得分:1)

基本上,架构是这样创建的:

class MyType(graphene.ObjectType):
    something = graphene.String()

class Query(graphene.ObjectType):
    value = graphene.Field(MyType)

schema = graphene.Schema(query=Query, types=[MyType])

首先,为了增加某种动态性,您很可能希望将上述代码包装在类似create_schema()的函数中。

然后,当您想在运行时动态创建类时,可以像上面这样重写上面的代码:

def create_schema():
    MyType = type('MyType', (graphene.ObjectType,), {
        'something': graphene.String(),
    })

    Query = type('Query', (graphene.ObjectType,), {
        'value': graphene.Field(MyType),
    })

    return graphene.Schema(query=Query, types=[MyType])

对于您的示例,它可能看起来像这样:

def make_resolver(record_name, record_cls):
    def resolver(self, info):
        data = ...
        return record_cls(...)
    resolver.__name__ = 'resolve_%s' % record_name
    return resolver

def create_schema(db):
    record_schemas = {}
    for record_type in db.get_record_types():
        classname = record_type['id'].title()  # 'Author'
        fields = {}
        for option in record_type['options']:
            field_type = {
                'text': graphene.String,
                ...
            }[option['type']
            fields[option['id']] = field_type()  # maybe add label as description?
        rec_cls = type(
            classname,
            (graphene.ObjectType,), 
            fields,
            name=record_type['name'],
            description=record_type['desc'],
        )
        record_schemas[record_type['id']] = rec_cls

    # create Query in similar way
    fields = {}
    for key, rec in record_schemas:
        fields[key] = graphene.Field(rec)
        fields['resolve_%s' % key] = make_resolver(key, rec)
    Query = type('Query', (graphene.ObjectType,), fields)

    return graphene.Schema(query=Query, types=list(record_schemas.values()))

请注意,如果您尝试将新字段插入到现有类中, 像这样-MyType.another_field = graphene.String(), 那就行不通了:这是因为在实例化graphene.ObjectType类时, 其所有字段都记录在self._meta.fields OrderedDict中。 并且更新它不像MyType._meta.fields['another_field'] = thefield那样简单明了-有关详细信息,请参见graphene.ObjectType.__init_subclass_with_meta__的代码。

因此,如果您的架构是动态更改的,那么最好是从头开始完全重新创建它,而不是对其进行修补。

答案 1 :(得分:0)

我想分享另一种巧妙的方法。

因此,问题在于 graphene.ObjectType 不是常规的 Python 类。它有一个特殊的 metaclass,您可以看到它实现了 here。目前 Python 负责继承过程(当类本身被初始化时),graphene 会执行一些操作来注册类型。我没有找到在继承发生后改变类型的方法。但是,如果您只想从预定义的样板(如我)或其他来源生成模式,您可以执行以下操作。我首先定义了一个方便的继承方法:

def inherit_from(Child, Parent, persist_meta=False):
    """Return a class that is equivalent to Child(Parent) including Parent bases."""
    PersistMeta = copy(Child.Meta) if hasattr(Child, 'Meta') else None

    if persist_meta:
        Child.Meta = PersistMeta

    # Prepare bases
    child_bases = inspect.getmro(Child)
    parent_bases = inspect.getmro(Parent)
    bases = tuple([item for item in parent_bases if item not in child_bases]) + child_bases

    # Construct the new return type
    try:
        Child = type(Child.__name__, bases, Child.__dict__.copy())
    except AttributeError as e:
        if str(e) == 'Meta':
            raise AttributeError('Attribute Error in graphene library. Try setting persist_meta=True in the inherit_from method call.')
        raise e

    if persist_meta:
        Child.Meta = PersistMeta

    return Child

现在关键是在类型的类不再改变时执行继承。

def context_resolver_factory(attr):
    """Create a simple resolver method with default return value None."""

    def resolver(obj, info):
        return info.context.get(attr, None)

    return resolver


class User:
    id = graphene.ID()
    name = graphene.String(resolver=lambda user, info: user.name)


class Query: pass
    me = graphene.Field(User)

    def resolve_me(self, info):
        return info.context["user"]


inherit_from(User, graphene.ObjectType)  # no changes to User class are possible after this line

# method 1: sometimes it's really neat and clean to include a resolver in the field definition
setattr(Query, 'user', graphene.User(resolver=context_resolver_factory('user'))
# or even use lambda if a factory is still overkill
setattr(Query, 'user', graphene.User(resolver=lambda query, info: info.context["user"]))


# method 2: if you want to set the resolver separately, you can do it this way
setattr(Query, 'user', graphene.User())
setattr(Query, 'resolve_user', context_resolver_factory('user'))

# any changes to `Query.Meta` can be done here too

inherit_from(Query, graphene.ObjectType)  # no changes to Query class are possible after this line

schema = graphene.Schema(query=Query)

我用我的小图书馆得到的地方是从这样的样板类中生成所有内容:

@register_type('Product')
class ProductType:
    class Meta:
        model = Product
        fields = '__all__'
        related_fields = {
            NestedField('tags', TagType),
            NestedField('related_products', 'self'),
        }
        lookups = {
            'id': graphene.ID(),
            'name': graphene.String(description="Name"),
            'ean': graphene.String(),
            'brand': graphene.String()
        }
        filters = {
            'ids': IDFilter,
            'django_filter': DjangoFilter,
            'pagination': PaginationFilter,
            'search_name': ProductMLNSearchFilter
        }

最大的挑战是 NestedFields 和在请求进来时找出自动 Django ORM 查询选择/预取,但除非相关,否则我不会详细介绍。