strawberry-django-plus icon indicating copy to clipboard operation
strawberry-django-plus copied to clipboard

How to use federation?

Open uroybd opened this issue 2 years ago • 8 comments

An example would've been nice showing how to use federation with Django models using this package.

uroybd avatar Jun 06 '22 09:06 uroybd

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?

bellini666 avatar Jun 06 '22 17:06 bellini666

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 avatar Jun 07 '22 06:06 uroybd

@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:

  1. Do what strawberry does and define a federation module that has its own field, which implements the django's one and also the federation ones.

  2. Don't do that at all and simply use the directives directly. e.g. instead of passing shareable=True to the type, you would pass directives=[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

bellini666 avatar Jun 07 '22 13:06 bellini666

@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:

  1. Do what strawberry does and define a federation module that has its own field, which implements the django's one and also the federation ones.

  2. Don't do that at all and simply use the directives directly. e.g. instead of passing shareable=True to the type, you would pass directives=[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.

uroybd avatar Jun 07 '22 13:06 uroybd

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)

uroybd avatar Jun 09 '22 11:06 uroybd

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 avatar Aug 09 '22 18:08 SpectralAngel

@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 avatar Aug 09 '22 22:08 bellini666

@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)

SpectralAngel avatar Aug 11 '22 19:08 SpectralAngel