-
-
Notifications
You must be signed in to change notification settings - Fork 533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Object Type Extensions #2605
Comments
I really like the idea! Speaking from the experience I have defining custom
1 can probably be solved by also adding an optional 2 can probably be solved by I wonder if the logic to add extra arguments wouldn't be too complicated in Also, if I understood correctly, that |
I prefer the first variant because it follows the extension approach closely by only extending the functionality and properties of a Regarding
Exactly, we don't need custom
I believe a great way to go forward with this would be to prototype |
@erikwrede I think this is a very good idea and would help expose any flaws in the API quickly. The pydantic integration would also be a good candidate for conversion and might be a simpler one to do first. |
@jkimbo |
Prototype (WIP) here: #2612 I also renamed the |
Hello, The biggest difference vs previous prototype is the ability to to change Sample usage might look something like this: class StrawberryDjangoField(StrawberryField):
def __init__(self, *args, model: DjangoModel, **kwargs):
super().__init__(*args, **kwargs)
self._model = model
class StrawberryDjangoObjectDefinition(StrawberryObjectDefinition):
model: DjangoModel
class DjangoTypeExtension(TypeExtension):
def __init__(
self,
model: DjangoModel,
field_class: type[StrawberryDjangoField],
fields: list[str] | None,
):
self._model = model
self._field_class = field_class
self._fields = fields
def before_wrap_dataclass(self, cls: type):
# add fields from model
for field_name in self._fields or []:
cls.__annotations__[field_name] = strawberry.auto
def on_field(self, field: dataclasses.Field[Any]):
if is_auto(field):
field = StrawberryField(
python_name=field.name,
graphql_name=None,
type_annotation=type_for_field(self._model, field.name),
)
return field
def create_object_definition(self, *args, **kwargs):
return StrawberryDjangoObjectDefinition(
*args, model=self._model, **kwargs
)
@strawberry.type(extension=DjangoTypeExtension(model=..., field_cls=..., fields=...))
class Something:
pass
# Or restrict to extension arguments
def django_type(
model: type[DjangoModel],
*,
name: str | None = None,
field_cls: type[StrawberryDjangoField] = StrawberryDjangoField,
is_input: bool = False,
is_interface: bool = False,
description: str | None = None,
directives: Sequence[object] | None = (),
extend: bool = False,
fields: list[str] | None = None,
):
extension = DjangoTypeExtension(
model=model,
field_cls=field_cls,
fields=fields,
)
return strawberry.type(
name=name,
is_input=is_input,
is_interface=is_interface,
description=description,
directives=directives,
extend=extend,
extension=extension ,
) EDIT: |
Currently, Strawberry lacks a mechanism for extending types with reusable logic, such as pagination, database model mappers for libraries like sqlalchemy, or compatibility extensions like strawberry.experimental.pydantic. Much of the logic necessary for these to work has to be written separately for each use case. Opinionating the way we extend strawberry types could standardize behavior and improve compatibility and maintainability. This issue proposes an
ObjectTypeExtension
API that provides a foundation to address the mentioned points.API proposal
ObjectTypeExtension
s provide custom functionality, can modify the underlyingTypeDefinition
or may offer the possibility to implement standardized hooks (more on that later).A
DjangoModelExtension
could useapply
to setup all the automatic model fields, similar to aPydanticModelExtension
orSQLAlchemyModelExtension
Similar to Field Extensions (#2168 , #2567),
ObjectTypeExtension
instances are passed to the@strawberry.type
annotation:will resolve to:
Extensions and Polymorphism
Using the annotation to define extensions is favorable over polymorphism of the actual
MyType
class, as that is adataclass
with resolver logic. The Extensions will provide behavioral logic and extended functionality and are a better fit forStrawberryType
. Extensions themselves support polymorphism. TheDjangoExtension
could natively supportOffsetPaginationExtension
orRelayPaginationExtension
.Initialization
Extensions are initialized after
TypeDefinition
initialization. Additionally, we can provide helper methods to make dealing with polymorphic extensions easier:Interacting with Object Type Extensions
A major API to interact with extensions will be the
FieldExtension
API. In cases like Pagination,FieldExtensions
have a synergy withObjectTypeExtensions
by defining the user-facing pagination logic on theFieldExtension
and using theObjectTypeExtension
to actually resolve the data. This way, only oneFieldExtension
is necessary to implementOffsetPagination
, which is compatible with bothDjangoModelExtension
,SQLAlchemyModelExtension
and more:resolves to
using
Using this approach streamlines user-facing behavior and helps standardize the internal logic for all extensions of strawberry. Filtering or sorting are other great options to use the combination of
FieldExtensions
andObjectTypeExtensions
Decisions
Dealing with type hints
We need to decide how to handle automatic database model-derived fields. Should we require
strawberry.auto
-typed fields on the actual types (similar to the current pydantic extension), or can the user just pass an empty object type?strawberry.auto
provides little benefit to the user in case of manual use, as it will not reveal any type information. As such, it might be better to only enfore its use in override-cases (e.g. change the default description, type or alias a model field)Implement hooks
Hooks such as
wrap_resolve
wrapping the resolver of each object type could provide additonal on-resolve functionality.Cases like unnecessary database calls just to resolve an ID field may be avoided using
wrap_resolve
by parsing theselection_set
before any resolver is actually called. However, their use might be an antipattern to the proposedObjectTypeExtension
+FieldExtension
synergy as it's easier and more explicit to implement that usingFieldExtension
s. My personal preference is to not provide standardized resolve-time hooks and implement that functionality usingFieldExtension
s instead.Upvote & Fund
The text was updated successfully, but these errors were encountered: