Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions graphql_api/helpers/connection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import enum
from base64 import b64decode
from dataclasses import dataclass
from functools import cached_property
from typing import Any, Dict, List, Optional

from cursor_pagination import CursorPage, CursorPaginator
from cursor_pagination import CursorPage, CursorPaginator, InvalidCursor
from django.db.models import QuerySet

from codecov.commands.exceptions import ValidationError
Expand Down Expand Up @@ -181,6 +182,7 @@


class DictCursorPaginator(CursorPaginator):
NULL_VALUE_REPR = "\x1f"
"""
WARNING: DictCursorPaginator does not work for dict objects where a key contains the following string: "__"
TODO: if instance is a dictionary and not an object, don't split the ordering
Expand All @@ -205,6 +207,17 @@
if the dict access fails then it throws an exception, although it would be a different
"""

def decode_cursor(self, cursor):
try:
orderings = b64decode(cursor.encode("ascii")).decode("utf8")
orderings = orderings.split(self.delimiter)
return [
None if ordering == self.NULL_VALUE_REPR else ordering
for ordering in orderings
]
except (TypeError, ValueError):
raise InvalidCursor(self.invalid_cursor_message)

Check warning on line 219 in graphql_api/helpers/connection.py

View check run for this annotation

Codecov Notifications / codecov/patch

graphql_api/helpers/connection.py#L218-L219

Added lines #L218 - L219 were not covered by tests

def position_from_instance(self, instance):
position = []
for order in self.ordering:
Expand All @@ -219,7 +232,7 @@
except (KeyError, TypeError):
raise attr_err from None
parts.pop(0)
position.append(str(attr))
position.append(self.NULL_VALUE_REPR if attr is None else str(attr))
return position


Expand Down
18 changes: 18 additions & 0 deletions graphql_api/helpers/tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,21 @@ def test_invalid_cursors(self):

with self.assertRaises(ValidationError):
queryset_to_connection_sync(data, first=3, after="invalid")

def test_dict_cursor_paginator_null_encoding(self):
from graphql_api.helpers.connection import DictCursorPaginator, field_order

repo_1 = RepositoryFactory(name="a", active=None)
repo_2 = RepositoryFactory(name="b", active=True)
repo_3 = RepositoryFactory(name="c", active=False)
r = Repository.objects.all()

ordering = tuple(
field_order(field, OrderingDirection.ASC) for field in ("active",)
)

paginator = DictCursorPaginator(r, ordering=ordering)

assert paginator.position_from_instance(repo_1) == ["\x1f"]
assert paginator.position_from_instance(repo_2) == ["True"]
assert paginator.position_from_instance(repo_3) == ["False"]
Loading