我花了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项目正在提供这个。
提前致谢!
答案 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 查询选择/预取,但除非相关,否则我不会详细介绍。