devrev-Python-SDK 3.0.0__py3-none-any.whl → 3.0.1__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.
@@ -0,0 +1,54 @@
1
+ """Shared pagination helpers for cursor-streaming list helpers.
2
+
3
+ The DevRev ``*.list`` endpoints cap per-page responses at 100 items and the
4
+ corresponding ``*ListRequest.limit`` fields are pydantic-constrained to
5
+ ``le=100``. Any service that drives cursor pagination from a user-supplied
6
+ ``overall_limit`` / ``page_size`` pair must therefore clamp the value it puts
7
+ on the request body before construction, or the request will fail validation
8
+ locally before it is ever sent.
9
+
10
+ :func:`resolve_page_limit` centralises that clamping so every ``list_*_since``
11
+ helper resolves the per-page ``limit`` identically.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Final
17
+
18
+ _MAX_PAGE: Final[int] = 100
19
+
20
+
21
+ def resolve_page_limit(
22
+ overall_limit: int | None,
23
+ collected: int,
24
+ page_size: int | None,
25
+ ) -> int | None:
26
+ """Compute the ``limit`` to send for the next page request.
27
+
28
+ The returned value is always clamped to :data:`_MAX_PAGE` so callers using
29
+ an ``overall_limit`` greater than the server maximum (e.g. ``limit=200,
30
+ page_size=None``) still paginate correctly. When both ``overall_limit`` and
31
+ ``page_size`` are supplied the tightest of ``{page_size, remaining,
32
+ _MAX_PAGE}`` wins.
33
+
34
+ Args:
35
+ overall_limit: Caller-supplied hard cap on total items to return across
36
+ all pages, or ``None`` to stream until the server runs out of
37
+ results.
38
+ collected: Number of items already accumulated across prior pages.
39
+ page_size: Caller-supplied per-page size, or ``None`` to defer to the
40
+ server default (still clamped to :data:`_MAX_PAGE`).
41
+
42
+ Returns:
43
+ The ``limit`` to put on the next ``*ListRequest``, or ``None`` to omit
44
+ it entirely (only when both ``overall_limit`` and ``page_size`` are
45
+ ``None``).
46
+ """
47
+ if overall_limit is None:
48
+ if page_size is None:
49
+ return None
50
+ return min(page_size, _MAX_PAGE)
51
+ remaining = overall_limit - collected
52
+ if page_size is None:
53
+ return min(remaining, _MAX_PAGE)
54
+ return min(page_size, remaining, _MAX_PAGE)
@@ -23,6 +23,7 @@ from devrev.models.conversations import (
23
23
  ConversationsUpdateRequest,
24
24
  ConversationsUpdateResponse,
25
25
  )
26
+ from devrev.services._pagination import resolve_page_limit
26
27
  from devrev.services.base import AsyncBaseService, BaseService
27
28
 
28
29
 
@@ -47,33 +48,6 @@ def _normalize_sort_by(sort_by: Sequence[str] | None) -> list[str] | None:
47
48
  return normalized
48
49
 
49
50
 
50
- # ``ConversationsListRequest.limit`` is pydantic-constrained to ``le=100``;
51
- # clamp any computed per-page limit so pagination loops do not construct a
52
- # request body that fails validation before it is ever sent.
53
- _CONVERSATIONS_MAX_PAGE = 100
54
-
55
-
56
- def _resolve_page_limit(
57
- overall_limit: int | None,
58
- collected: int,
59
- page_size: int | None,
60
- ) -> int | None:
61
- """Compute the ``limit`` to send for the next page request.
62
-
63
- The returned value is always clamped to ``_CONVERSATIONS_MAX_PAGE`` so
64
- callers using an ``overall_limit`` greater than the server maximum (e.g.
65
- ``limit=200, page_size=None``) still paginate correctly.
66
- """
67
- if overall_limit is None:
68
- if page_size is None:
69
- return None
70
- return min(page_size, _CONVERSATIONS_MAX_PAGE)
71
- remaining = overall_limit - collected
72
- if page_size is None:
73
- return min(remaining, _CONVERSATIONS_MAX_PAGE)
74
- return min(page_size, remaining, _CONVERSATIONS_MAX_PAGE)
75
-
76
-
77
51
  def _is_before_cutoff(modified_date: datetime | None, cutoff: datetime) -> bool:
78
52
  """Return True if ``modified_date`` is strictly older than ``cutoff``.
79
53
 
@@ -135,7 +109,7 @@ class ConversationsService(BaseService):
135
109
  while True:
136
110
  if limit is not None and len(results) >= limit:
137
111
  break
138
- request_limit = _resolve_page_limit(limit, len(results), page_size)
112
+ request_limit = resolve_page_limit(limit, len(results), page_size)
139
113
  request = ConversationsListRequest(
140
114
  cursor=cursor,
141
115
  limit=request_limit,
@@ -267,7 +241,7 @@ class AsyncConversationsService(AsyncBaseService):
267
241
  while True:
268
242
  if limit is not None and len(results) >= limit:
269
243
  break
270
- request_limit = _resolve_page_limit(limit, len(results), page_size)
244
+ request_limit = resolve_page_limit(limit, len(results), page_size)
271
245
  request = ConversationsListRequest(
272
246
  cursor=cursor,
273
247
  limit=request_limit,
devrev/services/works.py CHANGED
@@ -31,6 +31,7 @@ from devrev.models.works import (
31
31
  WorksUpdateResponse,
32
32
  WorkType,
33
33
  )
34
+ from devrev.services._pagination import resolve_page_limit
34
35
  from devrev.services.base import AsyncBaseService, BaseService
35
36
 
36
37
  if TYPE_CHECKING:
@@ -58,31 +59,6 @@ def _is_before_cutoff(timestamp: datetime | None, cutoff: datetime) -> bool:
58
59
  return False
59
60
 
60
61
 
61
- # ``WorksListRequest.limit`` is pydantic-constrained to ``le=100``; clamp any
62
- # computed per-page limit so pagination loops do not construct a request body
63
- # that fails validation before it is ever sent.
64
- _WORKS_MAX_PAGE = 100
65
-
66
-
67
- def _resolve_page_limit(
68
- overall_limit: int | None,
69
- collected: int,
70
- page_size: int | None,
71
- ) -> int | None:
72
- """Compute the ``limit`` to send for the next page request.
73
-
74
- The returned value is always clamped to ``_WORKS_MAX_PAGE`` so callers using
75
- an ``overall_limit`` greater than the server maximum (e.g. ``limit=200,
76
- page_size=None``) still paginate correctly.
77
- """
78
- if page_size is not None:
79
- return min(page_size, _WORKS_MAX_PAGE)
80
- if overall_limit is None:
81
- return None
82
- remaining = overall_limit - collected
83
- return min(remaining, _WORKS_MAX_PAGE)
84
-
85
-
86
62
  def _normalize_sort_by(sort_by: Sequence[str] | None) -> _StrList | None:
87
63
  """Normalize sort_by entries to the server-expected ``field:direction`` form.
88
64
 
@@ -351,7 +327,7 @@ class WorksService(BaseService):
351
327
  owned_by=owned_by,
352
328
  applies_to_part=applies_to_part,
353
329
  cursor=cursor,
354
- limit=_resolve_page_limit(limit, len(collected), page_size),
330
+ limit=resolve_page_limit(limit, len(collected), page_size),
355
331
  sort_by=sort_by,
356
332
  )
357
333
  stop = False
@@ -577,7 +553,7 @@ class AsyncWorksService(AsyncBaseService):
577
553
  owned_by=owned_by,
578
554
  applies_to_part=applies_to_part,
579
555
  cursor=cursor,
580
- limit=_resolve_page_limit(limit, len(collected), page_size),
556
+ limit=resolve_page_limit(limit, len(collected), page_size),
581
557
  sort_by=sort_by,
582
558
  )
583
559
  stop = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devrev-Python-SDK
3
- Version: 3.0.0
3
+ Version: 3.0.1
4
4
  Summary: A modern, type-safe Python SDK for the DevRev API
5
5
  Project-URL: Homepage, https://github.com/mgmonteleone/py-dev-rev
6
6
  Project-URL: Documentation, https://github.com/mgmonteleone/py-dev-rev
@@ -36,13 +36,14 @@ devrev/models/webhooks.py,sha256=DzrLJiW1GYWvfaB-aVPcYCf4XkEGHz3dkyzbBKKdhFI,365
36
36
  devrev/models/widgets.py,sha256=7WWN17_ySqnu1pjYSIS5B8J5dEaqkoB8X8GWzvI0ZYc,4577
37
37
  devrev/models/works.py,sha256=sHZ_eJ6eCadWxPkM6HGcmbkiGRDYT2RnH3QZRO4T3tY,11198
38
38
  devrev/services/__init__.py,sha256=LWy_0xGNH692d6Wc4S4E9v6AoxjgfExFMzFmbrL2NTY,3975
39
+ devrev/services/_pagination.py,sha256=J6AXu1P3iNRyC8_nH709Z8nOLhHeSctOLRmY_eYI6QA,2100
39
40
  devrev/services/accounts.py,sha256=X7FgcODex0XKLiV_VvXKDl2Jm8XsNpn9qp40oRjZqME,9704
40
41
  devrev/services/articles.py,sha256=xOpJOG9f29a5W3IFROplL1a9eJdGucbUFkK-MAIIYlc,41637
41
42
  devrev/services/artifacts.py,sha256=SJzIi5M4np0ENoOTTGEAcoqoRFMVd8pe-BCo9vvhYzk,14124
42
43
  devrev/services/base.py,sha256=pzUOkOA0kc7yxQQS8AP65L-inpW1WxpsYwtZwA_LXk4,7174
43
44
  devrev/services/brands.py,sha256=W6FB9XXTtEzGOfm9IBI6dqY8WXrMeH6urwjiZKrDVgI,5679
44
45
  devrev/services/code_changes.py,sha256=ahDniXCz54GGmTFmNh88heUg0mHQcIAgaqwlqm1CP6E,3199
45
- devrev/services/conversations.py,sha256=Kx1pOyBc1Wkk32PmPhjXlM2KnLlLuN5Ydzrm9y5iJhM,13635
46
+ devrev/services/conversations.py,sha256=Xw8hDZuwte57fH2Z3D19gd5nrm91OURcWkBrKAcbqAQ,12716
46
47
  devrev/services/dev_users.py,sha256=YDJUNk6Crz2TN_uwkWQIBmztIYrLd8EX3toNLdb_HEk,11870
47
48
  devrev/services/engagements.py,sha256=nT6AElHbFO40utp6R_QCGwTGunXjVci73I8YAtkqu8A,9094
48
49
  devrev/services/groups.py,sha256=0VpOKTwHiRjrKXrO1kdHo8V_z2I02GphYQhyDFnImxg,4960
@@ -62,7 +63,7 @@ devrev/services/timeline_entries.py,sha256=6nbVPcWlE3-ohLhvkM421eqYS5ztKtHmGsPek
62
63
  devrev/services/track_events.py,sha256=lI4wXkWu3uUuXtuRg1MGNkTZ7B0Lc1PjM8Kw-6sUnWc,1439
63
64
  devrev/services/uoms.py,sha256=AA3ymoHj24FIbsZpYC4tg2elSdQ3iINTVOz7MraZcj8,8163
64
65
  devrev/services/webhooks.py,sha256=-TSkcaya1y48WB24_vHd-bqO5xSqxRsCLiilncNzQZU,3917
65
- devrev/services/works.py,sha256=EUlDaUOttiRhvFLa0tkZXtgF3PpLLZlyUqjf3Hp4zRo,21239
66
+ devrev/services/works.py,sha256=hAK7ZUqLph5Db15p4RDbeRFLZUALAF5tCFOQSYytj_g,20456
66
67
  devrev/utils/__init__.py,sha256=NOrbpkjDVLH8n9xf-xpZJiIIa_GVI_6vqTm3E8L3Udw,857
67
68
  devrev/utils/content_converter.py,sha256=emRBLiVoOfDGpPDzrMRnqQr4-QkqN13OdWlYOyU_LCg,28141
68
69
  devrev/utils/deprecation.py,sha256=7qB2Dx531oP7mNi7q2txOYsOKC9YwdHqlKPMFHOW9Ws,1275
@@ -116,7 +117,7 @@ devrev_mcp/utils/don_id.py,sha256=PAVzOSgaiqJQSdo1fwohFHq2x7PDNUZoBTN4LFGYM48,62
116
117
  devrev_mcp/utils/errors.py,sha256=5mRAo76rJvvEVi6b1ZokPxDtX5JKkptaqmiYDLCkwBE,2110
117
118
  devrev_mcp/utils/formatting.py,sha256=6JssG5x1BxjdgSiQ8Ou3H-9Wo3wgWTWmejsrGez4wKc,2431
118
119
  devrev_mcp/utils/pagination.py,sha256=EOUgL-ZdSToM1Q-ydXmjhibsef5K1u1g3CaS9K8I2fY,1286
119
- devrev_python_sdk-3.0.0.dist-info/METADATA,sha256=X6qGjwru56IRRe8VAMJqh8AQTWKhjn0r4S_-GjGkbc4,40906
120
- devrev_python_sdk-3.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
121
- devrev_python_sdk-3.0.0.dist-info/entry_points.txt,sha256=XiV4J_yy0yzVZVxg7T66YERVIlqdPNp3O-NHTHkllqQ,63
122
- devrev_python_sdk-3.0.0.dist-info/RECORD,,
120
+ devrev_python_sdk-3.0.1.dist-info/METADATA,sha256=lTWWDMuQ7SvBTLsT1ape1IhGmY4b8OixjrHpqvkQL2o,40906
121
+ devrev_python_sdk-3.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
122
+ devrev_python_sdk-3.0.1.dist-info/entry_points.txt,sha256=XiV4J_yy0yzVZVxg7T66YERVIlqdPNp3O-NHTHkllqQ,63
123
+ devrev_python_sdk-3.0.1.dist-info/RECORD,,