Как динамически создать графеновый объект? Например, я хочу во время выполнения добавлять поля и преобразователи на основе файла конфигурации.

Я хочу иметь возможность динамически создавать сервер 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"])

В настоящее время я получаю None с этой попыткой. Вынимая часть, где я обновляю _meta.fields, я получаю «temp не является допустимым аргументом» для создания userobj. 'temp' является недопустимым аргументом ключевого слова для пользователя


person kalshafei    schedule 20.05.2019    source источник


Ответы (2)


В настоящее время ищет решения для той же проблемы. Возможный ответ здесь

person Jura Brazdil    schedule 04.12.2019

Итак, проблема в том, что graphene.ObjectType не является обычным классом Python. У него есть специальный метакласс, который, как вы видите, реализован здесь. В настоящий момент 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 при поступлении запроса, но я не буду вдаваться в подробности, если это не имеет значения.

person Jura Brazdil    schedule 18.03.2021