Skip to content

Commit

Permalink
fix(graphene): add a custom metaclass factory for interface types
Browse files Browse the repository at this point in the history
Interface types, such as `Question`, `Answer`, and `Task` have subclasses,
and connections using those types may return a mix of specific object types.
Examples are form -> questions, which may return different question types.

For these interface types, the `graphene` internals require access to
a `FooType.Meta._meta.registry`  property, to access the type registry.
Somehow, the graphene metaclass system does not automatically build this
up correctly, so we have to help a little bit to make it work
  • Loading branch information
winged committed Jan 30, 2024
1 parent 69f9ae7 commit d56f131
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 0 deletions.
32 changes: 32 additions & 0 deletions caluma/caluma_core/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,38 @@ def _should_include_filter(filt):
return filter_coll()


def InterfaceMetaFactory():
"""
Metaclass factory for interface types.
Build a meta class suitable for the schema type classes that represent
"interface" types (those that have concrete subclasses, but could be mixed
in a connection type, such as Question, Answer, and Task).
Usage:
>>> class Foo(Node, graphene.Interface):
>>> ...
>>> Meta = InterfaceMetaFactory()
"""

class _meta(graphene.types.interface.InterfaceOptions):
@classmethod
# This is kinda useless but required as graphene tries to freeze()
# it's meta class objects
def freeze(cls):
cls._frozen = True

# This is what we're actually "fixing": On non-Interface types,
# this somehow works (or isn't needed), but here, if _meta.registry
# is not set, the whole schema construction fails
registry = get_global_registry()

# We need a new type (= class) each time it's called, because reuse
# triggers some weird errors
return type("Meta", (), {"_meta": _meta})


def CollectionFilterSetFactory(filterset_class, orderset_class=None):
"""
Build single-filter filterset classes.
Expand Down
5 changes: 5 additions & 0 deletions caluma/caluma_form/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
CollectionFilterSetFactory,
DjangoFilterConnectionField,
DjangoFilterInterfaceConnectionField,
InterfaceMetaFactory,
)
from ..caluma_core.mutation import Mutation, UserDefinedPrimaryKeyMixin
from ..caluma_core.relay import extract_global_id
Expand Down Expand Up @@ -159,6 +160,8 @@ def get_queryset(cls, queryset, info):
def resolve_type(cls, instance, info):
return resolve_question(instance)

Meta = InterfaceMetaFactory()


class Option(FormDjangoObjectType):
meta = generic.GenericScalar()
Expand Down Expand Up @@ -810,6 +813,8 @@ class Answer(Node, graphene.Interface):
def resolve_type(cls, instance, info):
return resolve_answer(instance)

Meta = InterfaceMetaFactory()


class AnswerQuerysetMixin(object):
"""Mixin to combine all different answer types into one queryset."""
Expand Down
3 changes: 3 additions & 0 deletions caluma/caluma_workflow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
CollectionFilterSetFactory,
DjangoFilterConnectionField,
DjangoFilterInterfaceConnectionField,
InterfaceMetaFactory,
)
from ..caluma_core.mutation import Mutation, UserDefinedPrimaryKeyMixin
from ..caluma_core.types import (
Expand Down Expand Up @@ -97,6 +98,8 @@ def resolve_type(cls, instance, info):

return TASK_TYPE[instance.type]

Meta = InterfaceMetaFactory()


class TaskConnection(CountableConnectionBase):
class Meta:
Expand Down
15 changes: 15 additions & 0 deletions caluma/tests/__snapshots__/test_schema.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,21 @@
type DjangoDebug {
"""Executed SQL queries for this API query."""
sql: [DjangoDebugSQL]

"""Raise exceptions for this API query."""
exceptions: [DjangoDebugException]
}

"""Represents a single exception raised."""
type DjangoDebugException {
"""The class of the exception"""
excType: String!

"""The message of the exception"""
message: String!

"""The stack trace"""
stack: String!
}

"""Represents a single database query made to a Django managed DB."""
Expand Down

0 comments on commit d56f131

Please sign in to comment.