[英]Creating Dynamic Schema on Runtime Graphene
I almost spent 3 days to find a way for creating a dynamic schema in python graphene.我几乎花了 3 天的时间来寻找一种在 python 石墨烯中创建动态模式的方法。 the only related result I could find is the below link: https://github.com/graphql-python/graphene/blob/master/graphene/types/dynamic.py But I couldn't find any documentation for it.
我能找到的唯一相关结果是以下链接: https ://github.com/graphql-python/graphene/blob/master/graphene/types/dynamic.py 但我找不到任何文档。
The whole idea is to create a dynamic schema.整个想法是创建一个动态模式。 I want to provide a GraphQL compatible API that makes users able to query my contents even if Models are not defined in the code.
我想提供一个兼容 GraphQL 的 API,即使代码中没有定义模型,用户也可以查询我的内容。 In other words, I want to create Models on the fly.
换句话说,我想动态创建模型。 I have no idea about what shall I do.
我不知道我该怎么做。
It would be a great favor if you can provide an example for that.如果你能为此提供一个例子,那将是一个很大的帮助。
Update :更新 :
My Project is a Headless CMS which has a feature that users can create their own content types and I want to provide a GraphQL interface to make everything easier and more flexible.我的项目是一个无头 CMS,它具有用户可以创建自己的内容类型的功能,我想提供一个 GraphQL 界面来让一切变得更容易和更灵活。
Here is example of my Content Types in DB :这是我在 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"
}
]
}
And Here is Stored content in DB based on that content type :这是基于该内容类型在数据库中存储的内容:
{
"id": "9rqgbrox10",
"content_type": "author",
"data":{
"author_fname":"Jimmy",
"author_ename":"Hello"
}
}
Now as my Models are not declared in Code and they are completely in DB, I want to make my schemas on the fly and I don't know what is best the solution for this.现在,由于我的模型没有在代码中声明并且它们完全在数据库中,我想即时制作我的模式,我不知道什么是最好的解决方案。 I know there should be a way because the other Headless CMS Projects are providing this.
我知道应该有一种方法,因为其他无头 CMS 项目正在提供这种方法。
Thanks in advance!提前致谢!
Basically, schema is created something like this:基本上,架构是这样创建的:
class MyType(graphene.ObjectType):
something = graphene.String()
class Query(graphene.ObjectType):
value = graphene.Field(MyType)
schema = graphene.Schema(query=Query, types=[MyType])
First, in order to add some kind of dynamics, you will most likely want to wrap the above code in a function like create_schema()
.首先,为了添加某种动态,您很可能希望将上述代码包装在像
create_schema()
这样的函数中。
Then, when you want to dynamically create a class during runtime, the above code can be rewritten like this:然后,当你想在运行时动态创建一个类时,上面的代码可以改写成这样:
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])
For your example it could look something like this:对于您的示例,它可能看起来像这样:
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()))
Note that if you try to insert new fields into already existing class, like this - MyType.another_field = graphene.String()
, then it won't work: that is because when graphene.ObjectType
class is instantiated, all its fields are recorded in self._meta.fields
OrderedDict.请注意,如果您尝试将新字段插入到已经存在的类中,就像这样 -
MyType.another_field = graphene.String()
,那么它将不起作用:这是因为当graphene.ObjectType
类被实例化时,它的所有字段都会被记录在self._meta.fields
OrderedDict。 And updating it is not as straightforward as just MyType._meta.fields['another_field'] = thefield
- see the code of graphene.ObjectType.__init_subclass_with_meta__
for details.并且更新它并不像
MyType._meta.fields['another_field'] = thefield
- 有关详细信息,请参阅graphene.ObjectType.__init_subclass_with_meta__
的代码。
So if your schema is dynamically changed then it might be better to fully re-create it from scratch than to patch it.因此,如果您的架构是动态更改的,那么从头开始完全重新创建它可能比修补它更好。
I'd like to share another neat method.我想分享另一种巧妙的方法。
So, the issue is that the graphene.ObjectType is not a regular Python class.因此,问题在于 graphene.ObjectType 不是常规的 Python 类。 It has a special metaclass that you can see implemented here .
它有一个特殊的元类,您可以在这里看到它的实现。 At the moment Python takes care of the inheritance process (when the class itself is initalized), graphene does some operations to register the type.
在 Python 负责继承过程(当类本身被初始化时)时,石墨烯会执行一些操作来注册类型。 I didn't find a way to change types after the inheritance happens.
在继承发生后我没有找到改变类型的方法。 However, if you just want to generate the schema out of a predefined boilerplate (like me) or some other source, you can do something like this.
但是,如果您只想从预定义的样板(如我)或其他来源生成模式,您可以执行类似的操作。 I first define a handy inherit method:
我首先定义一个方便的继承方法:
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
Now the key is to perform the inheritance when the type's class is not about to change anymore.现在的关键是当类型的类不再改变时执行继承。
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)
Where I got with my little library was generating everything from a boilerplate class like this:我用我的小图书馆得到的东西是从这样的样板类生成的:
@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
}
Biggest challenge were the NestedFields and figuring out automatic Django ORM query select/prefetch when a request comes in, but I won't go into detail unless that's something relevant.最大的挑战是 NestedFields 并在请求进入时找出自动 Django ORM 查询选择/预取,但除非相关,否则我不会详细介绍。
My solution for django.我的 django 解决方案。
Dynamic search by application included in INSTALLED_APPS and graphql schema collection. INSTALLED_APPS 和 graphql 模式集合中包含的应用程序动态搜索。 You should stick to the common name in mutation or queries (it should contain a common occurrence of the word by which we will find the class [name_class_contains]. example: AnyNameMutationMixin => word MutationMixin must be present in all classes name that you want to add to mutate) you must also have the same paths names.py and mutate.py in applications
您应该坚持突变或查询中的通用名称(它应该包含我们将找到类 [name_class_contains] 的常见单词。例如:AnyNameMutationMixin => 单词 MutationMixin 必须出现在您想要的所有类名称中添加到 mutate)您还必须在应用程序中具有相同的路径 names.py 和 mutate.py
file dynamic_collect_graphene_scheme文件 dynamic_collect_graphene_scheme
import importlib
import inspect
import graphene
from django.conf import settings
def dynamic_inherit(name_cls: str, parent_class_list):
""" name_cls needed to keep the standard names Mutation и Query"""
class Mutate(*parent_class_list, graphene.ObjectType):
pass
class Query(*parent_class_list, graphene.ObjectType):
pass
if name_cls.lower() == "mutate":
return Mutate
elif name_cls.lower() == "query":
return Query
else:
raise ValueError('cls need choice [mutate,query]')
def collect_class(name_class_contains, path_module_from_app):
list_cls = []
for application in settings.INSTALLED_APPS:
application = application.split('.')[0] # if app.AppConfig del AppConfig
try:
file = importlib.import_module(application + path_module_from_app)
except ImportError:
pass
else:
all_class = inspect.getmembers(file, inspect.isclass)
for cls in all_class:
if cls[0].find(name_class_contains) != -1:
list_cls.append(cls[1])
return list_cls
file schema.py文件 schema.py
"""Global GraphQL Schema"""
from graphene import Schema
from main.dynamic_collect_graphene_scheme import dynamic_inherit, collect_class
schema = Schema(query=dynamic_inherit("query", collect_class(name_class_contains="MixinQuery",
path_module_from_app=".api.queries")),
mutation=dynamic_inherit("mutate", collect_class(name_class_contains="MixinMutation",
path_module_from_app=".api.mutate")))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.