Esempio n. 1
0
async def connection_from_gino_array_slice(
    array_slice,
    args: ConnectionArguments = None,
    slice_start: int = 0,
    array_length: int = None,
    array_slice_length: int = None,
    connection_type: Any = Connection,
    edge_type: Any = Edge,
    page_info_type: Any = PageInfo,
) -> Connection:
    """Create a connection object from a slice of the result set."""
    args = args or {}
    before = args.get("before")
    after = args.get("after")
    first = args.get("first")
    last = args.get("last")
    if array_slice_length is None:
        array_slice_length = len(array_slice)
    slice_end = slice_start + array_slice_length
    if array_length is None:
        array_length = slice_end
    before_offset = get_offset_with_default(before, array_length)
    after_offset = get_offset_with_default(after, -1)

    start_offset = max(slice_start - 1, after_offset, -1) + 1
    end_offset = min(slice_end, before_offset, array_length)

    if isinstance(first, int):
        if first < 0:
            raise ValueError("Argument 'first' must be a non-negative integer.")

        end_offset = min(end_offset, start_offset + first)
    if isinstance(last, int):
        if last < 0:
            raise ValueError("Argument 'last' must be a non-negative integer.")

        start_offset = max(start_offset, end_offset - last)

    # If supplied slice is too large, trim it down before mapping over it.
    trimmed_slice = await array_slice[
        start_offset - slice_start : array_slice_length - (slice_end - end_offset)
    ].gino.all()

    edges = [
        edge_type(node=value, cursor=offset_to_cursor(start_offset + index))
        for index, value in enumerate(trimmed_slice)
    ]
    first_edge_cursor = edges[0].cursor if edges else None
    last_edge_cursor = edges[-1].cursor if edges else None
    upper_bound = before_offset if before else array_length

    return connection_type(
        edges=edges,
        page_info=page_info_type(
            start_cursor=first_edge_cursor,
            end_cursor=last_edge_cursor,
            has_previous_page=start_offset > 0,
            has_next_page=isinstance(first, int) and end_offset < upper_bound,
        ),
    )
Esempio n. 2
0
    def resolve_connection(cls, connection, args, iterable, max_limit=None):
        iterable = maybe_queryset(iterable)

        if isinstance(iterable, QuerySet):
            list_length = iterable.count()
            list_slice_length = (min(max_limit, list_length)
                                 if max_limit is not None else list_length)
        else:
            list_length = len(iterable)
            list_slice_length = (min(max_limit, list_length)
                                 if max_limit is not None else list_length)

        # If after is higher than list_length, connection_from_list_slice
        # would try to do a negative slicing which makes django throw an
        # AssertionError
        after = min(
            get_offset_with_default(args.get("after"), -1) + 1, list_length)

        if max_limit is not None and "first" not in args:
            args["first"] = max_limit

        connection = connection_from_list_slice(
            iterable[after:],
            args,
            slice_start=after,
            list_length=list_length,
            list_slice_length=list_slice_length,
            connection_type=connection,
            edge_type=connection.Edge,
            pageinfo_type=PageInfo,
        )
        connection.iterable = iterable
        connection.length = list_length
        return connection
Esempio n. 3
0
    def resolve_connection(cls, connection, args, iterable, max_limit=None):
        iterable = maybe_queryset(iterable)
        if isinstance(iterable, QuerySet):
            # only query count on database when pagination is needed
            # resolve_connection may be removed again once following issue is fixed:
            # https://github.com/graphql-python/graphene-django/issues/177
            if all(
                    args.get(x) is None
                    for x in ["before", "after", "first", "last"]):
                _len = len(iterable)
            else:
                _len = iterable.count()
        else:  # pragma: no cover
            _len = len(iterable)

        # If after is higher than list_length, connection_from_list_slice
        # would try to do a negative slicing which makes django throw an
        # AssertionError
        after = min(get_offset_with_default(args.get("after"), -1) + 1, _len)
        if max_limit is not None and "first" not in args:  # pragma: no cover
            args["first"] = max_limit

        connection = connection_from_list_slice(
            iterable[after:],
            args,
            slice_start=0,
            list_length=_len,
            list_slice_length=_len,
            connection_type=connection,
            edge_type=connection.Edge,
            pageinfo_type=PageInfo,
        )
        connection.iterable = iterable
        connection.length = _len
        return connection
Esempio n. 4
0
    def resolve_connection(cls, connection, args, iterable, max_limit=None):
        iterable = maybe_queryset(iterable)

        if isinstance(iterable, QuerySet):
            list_length = iterable.count()
            list_slice_length = (min(max_limit, list_length)
                                 if max_limit is not None else list_length)
        else:
            list_length = len(iterable)
            list_slice_length = (min(max_limit, list_length)
                                 if max_limit is not None else list_length)

        after = get_offset_with_default(args.get("after"), -1) + 1

        if max_limit is not None and "first" not in args:
            args["first"] = max_limit

        connection = connection_from_list_slice(
            iterable[after:],
            args,
            slice_start=after,
            list_length=list_length,
            list_slice_length=list_slice_length,
            connection_type=connection,
            edge_type=connection.Edge,
            pageinfo_type=PageInfo,
        )
        connection.iterable = iterable
        connection.length = list_length
        return connection
Esempio n. 5
0
    def resolve_connection(cls, connection, args, iterable, max_limit=None):
        # Remove the offset parameter and convert it to an after cursor.
        offset = args.pop("offset", None)
        after = args.get("after")
        if offset:
            if after:
                offset += cursor_to_offset(after) + 1
            # input offset starts at 1 while the graphene offset starts at 0
            args["after"] = offset_to_cursor(offset - 1)

        iterable = maybe_queryset(iterable)

        if isinstance(iterable, QuerySet):
            list_length = iterable.count()
        else:
            list_length = len(iterable)
        list_slice_length = (min(max_limit, list_length)
                             if max_limit is not None else list_length)

        # If after is higher than list_length, connection_from_list_slice
        # would try to do a negative slicing which makes django throw an
        # AssertionError
        after = min(
            get_offset_with_default(args.get("after"), -1) + 1, list_length)

        if max_limit is not None and "first" not in args:
            if "last" in args:
                args["first"] = list_length
                list_slice_length = list_length
            else:
                args["first"] = max_limit

        connection = connection_from_list_slice(
            iterable[after:],
            args,
            slice_start=after,
            list_length=list_length,
            list_slice_length=list_slice_length,
            connection_type=connection,
            edge_type=connection.Edge,
            pageinfo_type=PageInfo,
        )
        connection.iterable = iterable
        connection.length = list_length
        return connection
Esempio n. 6
0
def resolve_start_offset(slice_start, after):
    after_offset = get_offset_with_default(after, -1)
    return max(slice_start - 1, after_offset, -1) + 1
Esempio n. 7
0
def connection_from_list_slice(
    list_slice,
    args=None,
    connection_type=None,
    edge_type=None,
    pageinfo_type=None,
    slice_start=0,
    list_length=0,
    list_slice_length=None,
):
    """
    Replace graphql_relay.connection.arrayconnection.connection_from_list_slice.

    This can be removed, when (or better if)
    https://github.com/graphql-python/graphql-relay-py/issues/12
    is resolved.

    Given a slice (subset) of an array, returns a connection object for use in
    GraphQL.
    This function is similar to `connectionFromArray`, but is intended for use
    cases where you know the cardinality of the connection, consider it too large
    to materialize the entire array, and instead wish pass in a slice of the
    total result large enough to cover the range specified in `args`.
    """
    connection_type = connection_type or Connection
    edge_type = edge_type or Edge
    pageinfo_type = pageinfo_type or PageInfo

    args = args or {}

    before = args.get("before")
    after = args.get("after")
    first = args.get("first")
    last = args.get("last")
    if list_slice_length is None:  # pragma: no cover
        list_slice_length = len(list_slice)
    slice_end = slice_start + list_slice_length
    before_offset = get_offset_with_default(before, list_length)
    after_offset = get_offset_with_default(after, -1)

    start_offset = max(slice_start - 1, after_offset, -1) + 1
    end_offset = min(slice_end, before_offset, list_length)
    if isinstance(first, int):
        end_offset = min(end_offset, start_offset + first)
    if isinstance(last, int):
        start_offset = max(start_offset, end_offset - last)

    # If supplied slice is too large, trim it down before mapping over it.
    _slice = list_slice[max(start_offset - slice_start, 0):list_slice_length -
                        (slice_end - end_offset)]
    edges = [
        edge_type(node=node, cursor=offset_to_cursor(start_offset + i))
        for i, node in enumerate(_slice)
    ]

    first_edge_cursor = edges[0].cursor if edges else None
    last_edge_cursor = edges[-1].cursor if edges else None

    return connection_type(
        edges=edges,
        page_info=pageinfo_type(
            start_cursor=first_edge_cursor,
            end_cursor=last_edge_cursor,
            has_previous_page=start_offset > 0,
            has_next_page=end_offset < list_length,
        ),
    )
def connection_from_list_slice(list_slice,
                               args=None,
                               connection_type=None,
                               edge_type=None,
                               pageinfo_type=None,
                               slice_start=0,
                               list_length=0,
                               list_slice_length=None,
                               connection_field=None):
    """
    Given a slice (subset) of an array, returns a connection object for use in
    GraphQL.
    This function is similar to `connectionFromArray`, but is intended for use
    cases where you know the cardinality of the connection, consider it too
    large to materialize the entire array, and instead wish pass in a slice of
    the total result large enough to cover the range specified in `args`.
    """
    connection_type = connection_type or Connection
    edge_type = edge_type or Edge
    pageinfo_type = pageinfo_type or PageInfo

    args = args or {}

    before = args.get('before')
    after = args.get('after')
    first = args.get('first')
    last = args.get('last')

    enforce_first_or_last = args.get("enforce_first_or_last")
    max_limit = args.get("max_limit")

    if enforce_first_or_last:
        assert first or last, (
            "You must provide a `first` or `last` value to properly "
            "paginate the `{}` connection.").format(connection_type)

    if max_limit:
        if first or last:
            if first:
                assert first <= max_limit, (
                    "Requesting {} records on the `{}` connection exceeds "
                    "the `first` limit of {} records.").format(
                        first, connection_type, max_limit)
                first = args["first"] = min(first, max_limit)

            if last:
                assert last <= max_limit, (
                    "Requesting {} records on the `{}` connection exceeds "
                    "the `last` limit of {} records.").format(
                        last, connection_type, max_limit)
                last = args["last"] = min(last, max_limit)
        else:
            first = max_limit

    if list_slice_length is None:
        list_slice_length = len(list_slice)
    slice_end = slice_start + list_slice_length
    before_offset = get_offset_with_default(before, list_length)
    after_offset = get_offset_with_default(after, -1)

    start_offset = max(slice_start - 1, after_offset, -1) + 1
    end_offset = min(slice_end, before_offset, list_length)
    if isinstance(first, int):
        end_offset = min(end_offset, start_offset + first)
    if isinstance(last, int):
        start_offset = max(start_offset, end_offset - last)

    # If supplied slice is too large, trim it down before mapping over it.
    _slice_qs = list_slice[max(start_offset -
                               slice_start, 0):list_slice_length -
                           (slice_end - end_offset)]
    logger.debug_json(_slice_qs.to_dict())

    _slice = _slice_qs.execute()

    edges = [
        edge_type(node=node, cursor=offset_to_cursor(start_offset + i))
        for i, node in enumerate(_slice)
    ]

    first_edge_cursor = edges[0].cursor if edges else None
    last_edge_cursor = edges[-1].cursor if edges else None
    lower_bound = after_offset + 1 if after else 0
    upper_bound = before_offset if before else list_length

    conn = connection_type(
        edges=edges,
        page_info=pageinfo_type(
            start_cursor=first_edge_cursor,
            end_cursor=last_edge_cursor,
            has_previous_page=(isinstance(last, int)
                               and start_offset > lower_bound),
            has_next_page=isinstance(first, int) and end_offset < upper_bound),
    )

    # This is certainly something to consider to change. Although there are
    # some original `graphene` code parts that do alter the connection object
    # directly, in principle, we shouldn't apply anything in this way, but
    # rather include all the things by overriding appropriate parts,
    # especially the connection class. However, at the moment of writing,
    # due to, perhaps, too little investigation on how to do it properly with
    # `graphene`, this seems to be the most simple and appropriate solution.
    for backend_cls in connection_field.filter_backends:
        if backend_cls.has_connection_fields:
            backend = backend_cls(connection_field)
            backend.alter_connection(conn, _slice)

    return conn