Changeset View
Changeset View
Standalone View
Standalone View
swh/graphql/resolvers/base_connection.py
# Copyright (C) 2022 The Software Heritage developers | # Copyright (C) 2022 The Software Heritage developers | ||||
# See the AUTHORS file at the top-level directory of this distribution | # See the AUTHORS file at the top-level directory of this distribution | ||||
# License: GNU General Public License version 3, or any later version | # License: GNU General Public License version 3, or any later version | ||||
# See top-level LICENSE file for more information | # See top-level LICENSE file for more information | ||||
from abc import ABC, abstractmethod | from abc import ABC, abstractmethod | ||||
import binascii | |||||
from dataclasses import dataclass | from dataclasses import dataclass | ||||
from typing import Any, Optional, Type | from typing import Any, Optional, Type | ||||
from swh.graphql.errors import PaginationError | |||||
from swh.graphql.utils import utils | from swh.graphql.utils import utils | ||||
from .base_node import BaseNode | from .base_node import BaseNode | ||||
@dataclass | @dataclass | ||||
class PageInfo: | class PageInfo: | ||||
hasNextPage: bool | hasNextPage: bool | ||||
endCursor: str | endCursor: str | ||||
@dataclass | @dataclass | ||||
class ConnectionEdge: | class ConnectionEdge: | ||||
node: Any | node: Any | ||||
cursor: str | cursor: str | ||||
class BaseConnection(ABC): | class BaseConnection(ABC): | ||||
""" | """ | ||||
Base resolver for all the connections | Base resolver for all the connections | ||||
""" | """ | ||||
_node_class: Optional[Type[BaseNode]] = None | _node_class: Optional[Type[BaseNode]] = None | ||||
_page_size = 50 # default page size | _page_size = 50 # default page size | ||||
_max_page_size = 1000 # maximum value for the first arg | |||||
def __init__(self, obj, info, paged_data=None, **kwargs): | def __init__(self, obj, info, paged_data=None, **kwargs): | ||||
self.obj = obj | self.obj = obj | ||||
self.info = info | self.info = info | ||||
self.kwargs = kwargs | self.kwargs = kwargs | ||||
self._paged_data = paged_data | self._paged_data = paged_data | ||||
def __call__(self, *args, **kw): | def __call__(self, *args, **kw): | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | def _get_edges(self): | ||||
""" | """ | ||||
Return the list of connection edges, each with a cursor | Return the list of connection edges, each with a cursor | ||||
""" | """ | ||||
return [ | return [ | ||||
ConnectionEdge(node=node, cursor=self._get_index_cursor(index, node)) | ConnectionEdge(node=node, cursor=self._get_index_cursor(index, node)) | ||||
for (index, node) in enumerate(self.nodes) | for (index, node) in enumerate(self.nodes) | ||||
] | ] | ||||
def _get_after_arg(self): | def _get_after_arg(self) -> str: | ||||
""" | """ | ||||
Return the decoded next page token | Return the decoded next page token. Override to support a different | ||||
override to use a specific token | cursor type | ||||
""" | """ | ||||
return utils.get_decoded_cursor(self.kwargs.get("after")) | # different cursor is used in SnapshotBranchConnection | ||||
try: | |||||
def _get_first_arg(self): | cursor = utils.get_decoded_cursor(self.kwargs.get("after")) | ||||
""" | except (UnicodeDecodeError, binascii.Error, Exception) as e: | ||||
anlambert: Using `except Exception as e:` is sufficient here as all caught exceptions derive from it. | |||||
jayeshvAuthorUnsubmitted Done Inline ActionsI added those to be explicit about possible exceptions. This will also let us handle different errors and I think is a preferred practice. jayeshv: I added those to be explicit about possible exceptions. This will also let us handle different… | |||||
anlambertUnsubmitted Not Done Inline ActionsHonestly I do not see the point to proceed like this if you have the same error handling for all caught exceptions but whatever. anlambert: Honestly I do not see the point to proceed like this if you have the same error handling for… | |||||
jayeshvAuthorUnsubmitted Done Inline ActionsSorry, I removed the base exception in another diff D7986 jayeshv: Sorry, I removed the base exception in another diff D7986 | |||||
page_size is set to 50 by default | raise PaginationError("Invalid value for argument 'after'", errors=e) | ||||
""" | return cursor | ||||
return self.kwargs.get("first", self._page_size) | |||||
def _get_first_arg(self) -> int: | |||||
""" """ | |||||
# page_size is set to 50 by default | |||||
# Input type check is not required; It is defined in schema as an int | |||||
first = self.kwargs.get("first", self._page_size) | |||||
if first < 0 or first > self._max_page_size: | |||||
raise PaginationError( | |||||
"Value for argument 'first' is either too big or invalid" | |||||
anlambertUnsubmitted Done Inline ActionsYou could indicate the max page size in error message here. anlambert: You could indicate the max page size in error message here. | |||||
) | |||||
return first | |||||
def _get_index_cursor(self, index: int, node: Any): | def _get_index_cursor(self, index: int, node: Any): | ||||
""" | """ | ||||
Get the cursor to the given item index | Get the cursor to the given item index | ||||
""" | """ | ||||
# default implementation which works with swh-storage pagaination | # default implementation which works with swh-storage pagaination | ||||
# override this function to support other types (eg: SnapshotBranchConnection) | # override this function to support other types (eg: SnapshotBranchConnection) | ||||
offset_index = self._get_after_arg() or "0" | offset_index = self._get_after_arg() or "0" | ||||
index_cursor = int(offset_index) + index | index_cursor = int(offset_index) + index | ||||
return utils.get_encoded_cursor(str(index_cursor)) | return utils.get_encoded_cursor(str(index_cursor)) |
Using except Exception as e: is sufficient here as all caught exceptions derive from it.