strawberry-django-plus
strawberry-django-plus copied to clipboard
How to use federation?
An example would've been nice showing how to use federation with Django models using this package.
Hi @uroybd ,
I don't use federation so I'm not aware of any differences for django, but woudln't the official strawberry's documentation "just work"? If not, do you know which the differences would have to be documented?
Actually, it doesn't work out of the box. However, with these modifications it works:
@__dataclass_transform__(
order_default=True,
field_descriptors=(
StrawberryField,
_field,
node,
connection,
field.field,
field.node,
field.connection,
),
)
def type( # noqa:A001
model: Type[Model],
*,
name: Optional[str] = None,
is_input: bool = False,
is_interface: bool = False,
is_filter: Union[Literal["lookups"], bool] = False,
description: Optional[str] = None,
directives: Optional[Sequence[object]] = [],
extend: bool = False,
filters: Optional[type] = UNSET,
pagination: Optional[bool] = UNSET,
order: Optional[type] = UNSET,
only: Optional[TypeOrSequence[str]] = None,
select_related: Optional[TypeOrSequence[str]] = None,
prefetch_related: Optional[TypeOrSequence[PrefetchType]] = None,
disable_optimization: bool = False,
keys: List[Union["Key", str]] = None,
shareable: bool = False,
) -> Callable[[_T], _T]:
"""Annotates a class as a Django GraphQL type.
Examples:
It can be used like this:
>>> @gql.django.type(SomeModel)
... class X:
... some_field: gql.auto
... otherfield: str = gql.django.field()
"""
def wrapper(cls):
from strawberry.federation.schema_directives import Key, Shareable
directives.extend([Key(key) if isinstance(key, str) else key for key in keys or []])
if shareable:
directives.append(Shareable())
return _process_type(
cls,
model,
name=name,
is_input=is_input,
is_filter=is_filter,
is_interface=is_interface,
description=description,
directives=directives,
extend=extend,
filters=filters,
pagination=pagination,
order=order,
only=only,
select_related=select_related,
prefetch_related=prefetch_related,
disable_optimization=disable_optimization,
)
return wrapper
@uroybd oh, I got the issue now. strawberry has its own federation type, which expects a lot of extra parameters.
We have two ways to go here:
-
Do what
strawberry
does and define afederation
module that has its ownfield
, which implements the django's one and also the federation ones. -
Don't do that at all and simply use the directives directly. e.g. instead of passing
shareable=True
to thetype
, you would passdirectives=[Shareable()]
. Is there any downsides to this? As I mentioned, I don't use federation so I'm not aware on how much time is saved to have a shortcut like this
@uroybd oh, I got the issue now. strawberry has its own federation type, which expects a lot of extra parameters.
We have two ways to go here:
Do what
strawberry
does and define afederation
module that has its ownfield
, which implements the django's one and also the federation ones.Don't do that at all and simply use the directives directly. e.g. instead of passing
shareable=True
to thetype
, you would passdirectives=[Shareable()]
. Is there any downsides to this? As I mentioned, I don't use federation so I'm not aware on how much time is saved to have a shortcut like this
I'll give the 2nd option a try tomorrow. If it works, I believe it will be a cleaner solution.
Just checked. Using directives works just fine.
Now that it is sorted. A section in the doc might come handly for others. Here's the relevant snippets:
# users/types.py
import strawberry
from strawberry_django_plus import gql
from . import models
from strawberry.federation.schema_directives import Key
@gql.django.type(models.User, directives=[Key("id")])
class UserType:
username: gql.auto
email: gql.auto
first_name: gql.auto
last_name: gql.auto
is_staff: gql.auto
is_superuser: gql.auto
is_active: gql.auto
date_joined: gql.auto
id: strawberry.ID
id_attr = "id"
@gql.django.field(only=["first_name", "last_name"])
def full_name(self, root: models.User) -> str:
return f"{root.first_name or ''} {root.last_name or ''}".strip()
# pets/types.py
import strawberry
from strawberry_django_plus import gql
from . import models
from typing import List
from asgiref.sync import sync_to_async
from strawberry.federation.schema_directives import Key
# @federated.type(models.Pet, keys=["id"])
@gql.django.type(models.Pet, directives=[Key("id")])
class PetType:
owner: gql.auto
name: gql.auto
id: strawberry.ID
id_attr = "id"
# @sync_to_async
# async def get_owners_pets(root: "UserType") -> List[PetType]:
# return await sync_to_async(models.Pet.objects.filter, thread_sensitive=True)(owner=root.id)
@strawberry.federation.type(keys=["id"], extend=True)
class UserType:
id: strawberry.ID = strawberry.federation.field(external=True)
# pets: List[PetType] = strawberry.field(resolver=get_owners_pets)
@strawberry.field
async def pets(self, root: "UserType") -> List[PetType]:
return await sync_to_async(models.Pet.objects.filter, thread_sensitive=True)(owner=root.id)
@classmethod
def resolve_reference(cls, id: strawberry.ID):
return UserType(id=id)
Using the directives is now raising an exception with strawberry 0.124.0 and strawberry-django-plus 1.22.0
site-packages\strawberry_django_plus_init_.py", line 43, in _process_type d.instance.register(ret._type_definition) AttributeError: 'Key' object has no attribute 'register'
@SpectralAngel are you sure you are using the latest version of strawberry-django-plus? You didn't post the whole traceback, but if you check the line of your traceback it is happening on line 43 when trying to do d.instance.register(ret._type_definition)
. That same code is actually at line 35 currently and is defined as d.register(ret._type_definition)
(without the .instance
): https://github.com/blb-ventures/strawberry-django-plus/blob/main/strawberry_django_plus/init.py#L35
@bellini666 It seems that my laptop had conflicting venvs, after correcting it, it was working again! (Note to self, DONT develop in two different machines)