strawberry-graphql 0.233.3__py3-none-any.whl → 0.234.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
strawberry/relay/types.py CHANGED
@@ -38,7 +38,12 @@ from strawberry.utils.aio import aenumerate, aislice, resolve_awaitable
38
38
  from strawberry.utils.inspect import in_async_context
39
39
  from strawberry.utils.typing import eval_type, is_classvar
40
40
 
41
- from .utils import from_base64, should_resolve_list_connection_edges, to_base64
41
+ from .utils import (
42
+ SliceMetadata,
43
+ from_base64,
44
+ should_resolve_list_connection_edges,
45
+ to_base64,
46
+ )
42
47
 
43
48
  if TYPE_CHECKING:
44
49
  from strawberry.scalars import ID
@@ -790,61 +795,13 @@ class ListConnection(Connection[NodeType]):
790
795
  https://relay.dev/graphql/connections.htm#sec-Pagination-algorithm
791
796
 
792
797
  """
793
- max_results = info.schema.config.relay_max_results
794
- start = 0
795
- end: Optional[int] = None
796
-
797
- if after:
798
- after_type, after_parsed = from_base64(after)
799
- if after_type != PREFIX:
800
- # When the base64 hash doesnt exist, the after_type seems to return
801
- # arrayconnEction instead of PREFIX. Let's raise a predictable
802
- # instead of "An unknown error occurred."
803
- raise TypeError("Argument 'after' contains a non-existing value.")
804
-
805
- start = int(after_parsed) + 1
806
- if before:
807
- before_type, before_parsed = from_base64(before)
808
- if before_type != PREFIX:
809
- # When the base64 hash doesnt exist, the after_type seems to return
810
- # arrayconnEction instead of PREFIX. Let's raise a predictable
811
- # instead of "An unknown error occurred.
812
- raise TypeError("Argument 'before' contains a non-existing value.")
813
- end = int(before_parsed)
814
-
815
- if isinstance(first, int):
816
- if first < 0:
817
- raise ValueError("Argument 'first' must be a non-negative integer.")
818
-
819
- if first > max_results:
820
- raise ValueError(
821
- f"Argument 'first' cannot be higher than {max_results}."
822
- )
823
-
824
- if end is not None:
825
- start = max(0, end - 1)
826
-
827
- end = start + first
828
- if isinstance(last, int):
829
- if last < 0:
830
- raise ValueError("Argument 'last' must be a non-negative integer.")
831
-
832
- if last > max_results:
833
- raise ValueError(
834
- f"Argument 'last' cannot be higher than {max_results}."
835
- )
836
-
837
- if end is not None:
838
- start = max(start, end - last)
839
- else:
840
- end = sys.maxsize
841
-
842
- if end is None:
843
- end = start + max_results
844
-
845
- expected = end - start if end != sys.maxsize else None
846
- # Overfetch by 1 to check if we have a next result
847
- overfetch = end + 1 if end != sys.maxsize else end
798
+ slice_metadata = SliceMetadata.from_arguments(
799
+ info,
800
+ before=before,
801
+ after=after,
802
+ first=first,
803
+ last=last,
804
+ )
848
805
 
849
806
  type_def = get_object_definition(cls)
850
807
  assert type_def
@@ -863,15 +820,17 @@ class ListConnection(Connection[NodeType]):
863
820
  try:
864
821
  iterator = cast(
865
822
  Union[AsyncIterator[NodeType], AsyncIterable[NodeType]],
866
- cast(Sequence, nodes)[start:overfetch],
823
+ cast(Sequence, nodes)[
824
+ slice_metadata.start : slice_metadata.overfetch
825
+ ],
867
826
  )
868
827
  except TypeError:
869
828
  # TODO: Why mypy isn't narrowing this based on the if above?
870
829
  assert isinstance(nodes, (AsyncIterator, AsyncIterable))
871
830
  iterator = aislice(
872
831
  nodes,
873
- start,
874
- overfetch,
832
+ slice_metadata.start,
833
+ slice_metadata.overfetch,
875
834
  )
876
835
 
877
836
  # The slice above might return an object that now is not async
@@ -880,7 +839,7 @@ class ListConnection(Connection[NodeType]):
880
839
  edges: List[Edge] = [
881
840
  edge_class.resolve_edge(
882
841
  cls.resolve_node(v, info=info, **kwargs),
883
- cursor=start + i,
842
+ cursor=slice_metadata.start + i,
884
843
  )
885
844
  async for i, v in aenumerate(iterator)
886
845
  ]
@@ -888,17 +847,20 @@ class ListConnection(Connection[NodeType]):
888
847
  edges: List[Edge] = [ # type: ignore[no-redef]
889
848
  edge_class.resolve_edge(
890
849
  cls.resolve_node(v, info=info, **kwargs),
891
- cursor=start + i,
850
+ cursor=slice_metadata.start + i,
892
851
  )
893
852
  for i, v in enumerate(iterator)
894
853
  ]
895
854
 
896
- has_previous_page = start > 0
897
- if expected is not None and len(edges) == expected + 1:
855
+ has_previous_page = slice_metadata.start > 0
856
+ if (
857
+ slice_metadata.expected is not None
858
+ and len(edges) == slice_metadata.expected + 1
859
+ ):
898
860
  # Remove the overfetched result
899
861
  edges = edges[:-1]
900
862
  has_next_page = True
901
- elif end == sys.maxsize:
863
+ elif slice_metadata.end == sys.maxsize:
902
864
  # Last was asked without any after/before
903
865
  assert last is not None
904
866
  original_len = len(edges)
@@ -923,14 +885,14 @@ class ListConnection(Connection[NodeType]):
923
885
  try:
924
886
  iterator = cast(
925
887
  Union[Iterator[NodeType], Iterable[NodeType]],
926
- cast(Sequence, nodes)[start:overfetch],
888
+ cast(Sequence, nodes)[slice_metadata.start : slice_metadata.overfetch],
927
889
  )
928
890
  except TypeError:
929
891
  assert isinstance(nodes, (Iterable, Iterator))
930
892
  iterator = itertools.islice(
931
893
  nodes,
932
- start,
933
- overfetch,
894
+ slice_metadata.start,
895
+ slice_metadata.overfetch,
934
896
  )
935
897
 
936
898
  if not should_resolve_list_connection_edges(info):
@@ -947,17 +909,20 @@ class ListConnection(Connection[NodeType]):
947
909
  edges = [
948
910
  edge_class.resolve_edge(
949
911
  cls.resolve_node(v, info=info, **kwargs),
950
- cursor=start + i,
912
+ cursor=slice_metadata.start + i,
951
913
  )
952
914
  for i, v in enumerate(iterator)
953
915
  ]
954
916
 
955
- has_previous_page = start > 0
956
- if expected is not None and len(edges) == expected + 1:
917
+ has_previous_page = slice_metadata.start > 0
918
+ if (
919
+ slice_metadata.expected is not None
920
+ and len(edges) == slice_metadata.expected + 1
921
+ ):
957
922
  # Remove the overfetched result
958
923
  edges = edges[:-1]
959
924
  has_next_page = True
960
- elif end == sys.maxsize:
925
+ elif slice_metadata.end == sys.maxsize:
961
926
  # Last was asked without any after/before
962
927
  assert last is not None
963
928
  original_len = len(edges)
strawberry/relay/utils.py CHANGED
@@ -1,11 +1,17 @@
1
+ from __future__ import annotations
2
+
1
3
  import base64
2
- from typing import Any, Tuple, Union
3
- from typing_extensions import assert_never
4
+ import dataclasses
5
+ import sys
6
+ from typing import TYPE_CHECKING, Any, Tuple, Union
7
+ from typing_extensions import Self, assert_never
4
8
 
5
- from strawberry.types.info import Info
6
9
  from strawberry.types.nodes import InlineFragment, Selection
7
10
  from strawberry.types.types import StrawberryObjectDefinition
8
11
 
12
+ if TYPE_CHECKING:
13
+ from strawberry.types.info import Info
14
+
9
15
 
10
16
  def from_base64(value: str) -> Tuple[str, str]:
11
17
  """Parse the base64 encoded relay value.
@@ -102,3 +108,82 @@ def should_resolve_list_connection_edges(info: Info) -> bool:
102
108
  if _check_selection(selection):
103
109
  return True
104
110
  return False
111
+
112
+
113
+ @dataclasses.dataclass
114
+ class SliceMetadata:
115
+ start: int
116
+ end: int
117
+ expected: int | None
118
+
119
+ @property
120
+ def overfetch(self) -> int:
121
+ # Overfetch by 1 to check if we have a next result
122
+ return self.end + 1 if self.end != sys.maxsize else self.end
123
+
124
+ @classmethod
125
+ def from_arguments(
126
+ cls,
127
+ info: Info,
128
+ *,
129
+ before: str | None = None,
130
+ after: str | None = None,
131
+ first: int | None = None,
132
+ last: int | None = None,
133
+ ) -> Self:
134
+ """Get the slice metadata to use on ListConnection."""
135
+ from strawberry.relay.types import PREFIX
136
+
137
+ max_results = info.schema.config.relay_max_results
138
+ start = 0
139
+ end: int | None = None
140
+
141
+ if after:
142
+ after_type, after_parsed = from_base64(after)
143
+ if after_type != PREFIX:
144
+ raise TypeError("Argument 'after' contains a non-existing value.")
145
+
146
+ start = int(after_parsed) + 1
147
+ if before:
148
+ before_type, before_parsed = from_base64(before)
149
+ if before_type != PREFIX:
150
+ raise TypeError("Argument 'before' contains a non-existing value.")
151
+ end = int(before_parsed)
152
+
153
+ if isinstance(first, int):
154
+ if first < 0:
155
+ raise ValueError("Argument 'first' must be a non-negative integer.")
156
+
157
+ if first > max_results:
158
+ raise ValueError(
159
+ f"Argument 'first' cannot be higher than {max_results}."
160
+ )
161
+
162
+ if end is not None:
163
+ start = max(0, end - 1)
164
+
165
+ end = start + first
166
+ if isinstance(last, int):
167
+ if last < 0:
168
+ raise ValueError("Argument 'last' must be a non-negative integer.")
169
+
170
+ if last > max_results:
171
+ raise ValueError(
172
+ f"Argument 'last' cannot be higher than {max_results}."
173
+ )
174
+
175
+ if end is not None:
176
+ start = max(start, end - last)
177
+ else:
178
+ end = sys.maxsize
179
+
180
+ if end is None:
181
+ end = start + max_results
182
+
183
+ expected = end - start if end != sys.maxsize else None
184
+
185
+ return cls(
186
+ start=start,
187
+ end=end,
188
+ expected=expected,
189
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strawberry-graphql
3
- Version: 0.233.3
3
+ Version: 0.234.0
4
4
  Summary: A library for creating GraphQL APIs
5
5
  Home-page: https://strawberry.rocks/
6
6
  License: MIT
@@ -174,8 +174,8 @@ strawberry/quart/views.py,sha256=SDUaDX7bPKsv8PziMPb0C3nH6vre-Q3bhzSQp4uPjbY,340
174
174
  strawberry/relay/__init__.py,sha256=Vi4btvA_g6Cj9Tk_F9GCSegapIf2WqkOWV8y3P0cTCs,553
175
175
  strawberry/relay/exceptions.py,sha256=b7sU2MhHVWJOfq27lvqdFcqBZ5P_JWk41JWRjtP-AOI,3916
176
176
  strawberry/relay/fields.py,sha256=4V8jhPDECx0hPqtc0ZsNxvmhRYMf5whD_686O2-6_Dc,15934
177
- strawberry/relay/types.py,sha256=_bCYCDKLG_eCcTf66H6JsmQHX-8mEnpplxr82_2oJcI,30718
178
- strawberry/relay/utils.py,sha256=STuJ2j-sTPa70O9juJX21nbhK6yhnHdwiAOWBYKOVdo,3160
177
+ strawberry/relay/types.py,sha256=R7hCsLX0qR3pa4URLbtOeaMHr9EQwbpz-Vedr_PHCrU,29242
178
+ strawberry/relay/utils.py,sha256=44zl0_iXZElQ35YQkguccCGandKTovqiU5tpve9Ceb4,5575
179
179
  strawberry/resolvers.py,sha256=g7_g3jmXszziGydY1UG6IItf9s6B1lGLUCnwW1kb8U0,224
180
180
  strawberry/sanic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
181
181
  strawberry/sanic/context.py,sha256=qfoj8QMaAiWbCQFAdm2KPPJGNc7ilXeKAl5z0XJ2nzo,805
@@ -245,8 +245,8 @@ strawberry/utils/logging.py,sha256=flS7hV0JiIOEdXcrIjda4WyIWix86cpHHFNJL8gl1y4,7
245
245
  strawberry/utils/operation.py,sha256=Um-tBCPl3_bVFN2Ph7o1mnrxfxBes4HFCj6T0x4kZxE,1135
246
246
  strawberry/utils/str_converters.py,sha256=avIgPVLg98vZH9mA2lhzVdyyjqzLsK2NdBw9mJQ02Xk,813
247
247
  strawberry/utils/typing.py,sha256=G92wuT2WhEGQrwjek_On2K8l0nyVFtBW3P7I_cfjG-8,13870
248
- strawberry_graphql-0.233.3.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
249
- strawberry_graphql-0.233.3.dist-info/METADATA,sha256=ngYPqqY2aZA0575fAP5OcYlutX8qcVaqGnPPpdzXPjE,7821
250
- strawberry_graphql-0.233.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
251
- strawberry_graphql-0.233.3.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
252
- strawberry_graphql-0.233.3.dist-info/RECORD,,
248
+ strawberry_graphql-0.234.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
249
+ strawberry_graphql-0.234.0.dist-info/METADATA,sha256=b_WjywAyZWmvXZfQmNglw490pT-d-tEXYnmpAk51PWQ,7821
250
+ strawberry_graphql-0.234.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
251
+ strawberry_graphql-0.234.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
252
+ strawberry_graphql-0.234.0.dist-info/RECORD,,