如何动态创建石墨烯对象?例如,我想在运行时根据配置文件添加字段和解析器

Posted

技术标签:

【中文标题】如何动态创建石墨烯对象?例如,我想在运行时根据配置文件添加字段和解析器【英文标题】:How do I dynamically create a graphene Object? For example, I want to at run time add fields and resolvers based off of a configuration file 【发布时间】:2019-10-07 03:49:15 【问题描述】:

我希望能够从给定的配置文件动态创建 GraphQL 服务器。因此,例如,我的配置文件将包含应该存在的字段,并且某些字段将带有一个标志,指示它们是主键还是辅助键,这将映射到它们以获取解析器。 如何实现动态创建石墨烯对象类型?

我尝试获取石墨烯的示例代码并向其中添加字段。但它不会接受他们。我尝试深入研究元数据并更新一些选项,但也没有成功。

class User(graphene.ObjectType):
    id = graphene.ID()
    name = graphene.String()


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

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

schema = graphene.Schema(query=Query)
query = """
    query something
      me 
        name
        temp
      
    
"""

if __name__ == "__main__":
    userobj = User
    print(dir(userobj._meta.fields))
    setattr(userobj,"temp", graphene.String())
    print((userobj._meta.fields))

    userobj._meta.fields.update("temp": graphene.String())
    print(userobj._meta.fields)

    print((userobj._meta.fields))
    result = schema.execute(query, context="user": userobj(id="X", name="Console", temp="hey"))
    print(result.data)
    print(result.data["me"])

目前我没有通过这次尝试得到任何结果。取出我更新 _meta.fields 的部分,我得到用于创建 userobj 的“temp is not a valid argument”。 'temp' 是用户的无效关键字参数

【问题讨论】:

【参考方案1】:

目前正在寻找相同问题的解决方案。有一个可能的answer here

【讨论】:

【参考方案2】:

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

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 查询选择/预取,但除非相关,否则我不会详细介绍。

【讨论】:

以上是关于如何动态创建石墨烯对象?例如,我想在运行时根据配置文件添加字段和解析器的主要内容,如果未能解决你的问题,请参考以下文章

Python GraphQL 如何声明一个自引用的石墨烯对象类型

如何覆盖石墨烯中的 DjangoModelFormMutation 字段类型?

石墨烯错误消息

Django 石墨烯中继限制对用户拥有的对象的查询

用于嵌套自定义对象的石墨烯解析器

将 json 转换为石墨烯 graphql 响应