strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

Add pagination support

Open un33k opened this issue 5 years ago • 17 comments

[ x ] Feature Request [ x ] Documentation

If pagination is supported, be nice to include it in documentations. If there is no plan to support it, it'd be nice to provide an example for developers to role their own. Otherwise, let's keep this issue around to track the pagination feature.

un33k avatar Oct 08 '19 21:10 un33k

Right now there is no built-in support for pagination (as far as I know). It would definitely be neat to support this though!

One way to implement basic pagination would be to use something like this:

@strawberry.type
class Query:

    @strawberry.field
    def numbers(self, info, first: int = None, skip: int = None) -> typing.List[int]:
        numbers = range(1000)
        return numbers[skip:first]

This approach could be abstracted into a decorator for use in multiple queries as well.

If you'd like to add support for pagination using this approach, or write some documentation for it, a PR would be welcome :)

jaydenwindle avatar Oct 14 '19 19:10 jaydenwindle

@jaydenwindle thanks for answering 😊 at PyCon DE someone asked for Relay support, maybe we could write some helpers for that, I kinda this something here:

https://github.com/strawberry-graphql/strawberry-swapi/blob/master/utils.py

I think Graphene has something for arrays already, so we could import that. I don't really have much experience with Relay, so I'd happy to see someone working on this :)

patrick91 avatar Oct 14 '19 19:10 patrick91

Are there any simple examples of how to implement a decorator that's meant to be used in combination with @strawberry.field? I gave it a half-assed shot, but quickly got lost in type annotation jungle. I only recently started trying out this library, but it seems that type annotations are very central to the Strawberry library?

Any help would be appreciated, even just a simple example :) Sorry if this is the wrong place to ask.

denizdogan avatar Nov 24 '19 22:11 denizdogan

@denizdogan what did you try already? I'd like to know mainly because I'd like to make the library as user friendly as possible :)

I'll try to come up with an example tomorrow :)

patrick91 avatar Nov 24 '19 23:11 patrick91

@patrick91 As I said, I didn't really try very hard :) But I slapped together a simple example which shows where I get stuck:

def strawberry_limit(limit=100):
    def inner(func):
        def replacement(*args, **kwargs):
            output = inner(*args, **kwargs)
            return output[:limit]
        return replacement
    return inner

@strawberry.type
class UserType:
    uuid: str

@strawberry.type
class Query:
    @strawberry.field
    @strawberry_limit(limit=10)  # does order matter?
    def users(self, info) -> List[UserType]:
        return User.objects.all()

The above style gives me the following error:

strawberry.exceptions.MissingReturnAnnotationError: Return annotation missing for field "replacement", did you forget to add it?

So this is when I realize that I have to take care to not lose the type annotations, so I start off by using @functools.wraps for my decorator definition.

I tried again using the following:


def strawberry_limit(func):
    @wraps(func)
    def replacement(*args, limit: int = None, **kwargs):
        output = func(*args, **kwargs)
        if limit is not None:
            return output[:limit]
        return output

    return replacement


@strawberry.type
class Query:
    @strawberry_limit  # again, does order matter?
    @strawberry.field
    def users(self, info) -> List[UserType]:
        return User.objects.all()

The above doesn't raise any uncaught exception, but I do get:

{
  "data": null,
  "errors": [
    {
      "message": "Type Query must define one or more fields.",
      "locations": null,
      "path": null
    }
  ]
}

Am I close? :)

denizdogan avatar Nov 24 '19 23:11 denizdogan

oh, when we use @strawberry.field we add an attribute on the function, so that we can find it later (to created the field), but maybe this approach is not the best one as this is breaking in this case.

This is where we do it:

https://github.com/strawberry-graphql/strawberry/blob/master/strawberry/field.py#L175-L176

one solution (for now) would be do put that attribute yourself, but it is not ideal. I'll think of something better.

What happens if you switch the order in the second case? I'd try myself, but I'm on the phone right now :)

patrick91 avatar Nov 24 '19 23:11 patrick91

If I switch the order of the decorators, I get:

"message": "Unknown argument 'limit' on field 'users' of type 'Query'.",

Whenever you get the time and strength to give this a go, I'm very interested, but it's not really a pressing issue for me right now, so don't sweat it :)

I've been considering that maybe using a decorator for this kind of thing is not really the ideal way to implement something like pagination. I could see us wrapping the return value in some lazy wrapper, as such:

def users(self, info) -> Paginated[List[User]]:
    return Paginated(info, Users.objects.all(), limit=100)

Then the logic would happen in the Paginated class instead of the decorator. That way, we could avoid messing with the magic that strawberry.field implements and stay out of its way? Obviously, I haven't tried this way to know if it's more accessible than the decorator idea, but I thought I'd mention it. :)

denizdogan avatar Nov 24 '19 23:11 denizdogan

Quick update on this, we recently released support for generic types with the goal of making it easier to create pagination types. They'd work like this:

import strawberry


@strawberry.type
class PageInfo:
    hasNext: bool
    lastCursor: str


T = TypeVar("T")


@strawberry.type
class Paginated(Generic[T]):
    count: Optional[int] = None
    items: T

    @classmethod
    def paginate(cls, data: Sequence[T], page: int):
        ...

and you'd use them like this:

@strawberry.type
class Query:
     @strawberry.field
     def users(self, page: int) -> Paginated[User]:
          return Paginated.paginate(User.all(), page)

This would create the PaginatedUser type for you.

We plan to add relay pagination and a simple page pagination as built-in :)

patrick91 avatar May 21 '20 11:05 patrick91

Any progress on this?

memark avatar Oct 01 '21 19:10 memark

waiting for this to implement in my project

akhilputhiry avatar Oct 03 '21 05:10 akhilputhiry

@memark not yet, are you interested in a Relay pagination or basic pagination?

patrick91 avatar Oct 05 '21 08:10 patrick91

@patrick91 Basic pagination would be just fine!

memark avatar Oct 05 '21 08:10 memark

@memark cool! maybe I can write a guide, it shouldn't be too difficult to roll your own since we have support for generics. Would that help?

I wouldn't commit to an API design for basic pagination just yet 😊

patrick91 avatar Oct 05 '21 08:10 patrick91

@patrick91 Yes, that would be very helpful!

memark avatar Oct 05 '21 09:10 memark

I'm looking to move to strawberry from graphene but don't want to before pagination is supported. How far off is it? I see basic looks to be done. What about cursor based?

rossm6 avatar Nov 25 '21 15:11 rossm6

I'm looking to move to strawberry from graphene but don't want to before pagination is supported. How far off is it? I see basic looks to be done. What about cursor based?

Cursor based pagination can already be implemented in strawberry, the docs just need to be updated (#1345)

aryaniyaps avatar Jan 03 '22 00:01 aryaniyaps

could someone give me a cursor based pagination example ? thanks

vasilistotskas avatar Aug 26 '22 19:08 vasilistotskas

I'm going to close this as we now have docs for pagination (see https://github.com/strawberry-graphql/strawberry/pull/1345)

We can always include helpers in future (or in an external library), but I'd discuss those in a new issue :)

patrick91 avatar Jan 14 '23 10:01 patrick91