asana-api-cli 2.1.0__tar.gz → 2.1.1__tar.gz

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.
Files changed (25) hide show
  1. {asana_api_cli-2.1.0/src/asana_api_cli.egg-info → asana_api_cli-2.1.1}/PKG-INFO +1 -1
  2. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/pyproject.toml +1 -1
  3. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/cli.py +22 -37
  4. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/session.py +0 -49
  5. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1/src/asana_api_cli.egg-info}/PKG-INFO +1 -1
  6. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_cli.py +7 -40
  7. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_cli_invocation.py +59 -97
  8. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/LICENSE +0 -0
  9. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/README.md +0 -0
  10. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/setup.cfg +0 -0
  11. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/__init__.py +0 -0
  12. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/click_ext.py +0 -0
  13. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/formatter.py +0 -0
  14. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/version.py +0 -0
  15. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/SOURCES.txt +0 -0
  16. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/dependency_links.txt +0 -0
  17. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/entry_points.txt +0 -0
  18. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/requires.txt +0 -0
  19. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/top_level.txt +0 -0
  20. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_cli_surface.py +0 -0
  21. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_click_ext.py +0 -0
  22. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_formatter.py +0 -0
  23. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_py310_compat.py +0 -0
  24. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_session.py +0 -0
  25. {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asana-api-cli
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Command-line wrapper around the official Asana Python SDK
5
5
  Author-email: Masanao Izumo <asana@masanao.site>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asana-api-cli"
3
- version = "2.1.0"
3
+ version = "2.1.1"
4
4
  description = "Command-line wrapper around the official Asana Python SDK"
5
5
  authors = [{name = "Masanao Izumo", email = "asana@masanao.site"}]
6
6
  readme = "README.md"
@@ -276,23 +276,6 @@ def _operations_for(api_cls: type) -> list[_Operation]:
276
276
  # ---------------------------------------------------------------------------
277
277
 
278
278
 
279
- def _resolve_effective_page_size(page_size: int | None, max_items: int | None) -> int | None:
280
- """Return the per-page size to request when ``--max-items`` caps the total.
281
-
282
- Only shrink below the natural per-page size (100, Asana's max) when
283
- ``--max-items`` is *smaller* than what we would otherwise request — that
284
- way a single trailing page is not wasted. When ``--max-items`` is larger,
285
- keep the user's explicit ``--page-size`` (or fall back to the SDK default
286
- of 100); shrinking to ``--max-items`` would push the per-request ``limit``
287
- above the Asana API cap of 100 and produce a 400 response.
288
- """
289
- if max_items is None:
290
- return page_size
291
- if max_items < (page_size or 100):
292
- return max_items
293
- return page_size
294
-
295
-
296
279
  def _make_command(api_cls: type, op: _Operation) -> click.Command:
297
280
  """Build a :class:`CommandWithGlobalOptions` for a single SDK method."""
298
281
  # If the SDK method has no ``opts`` parameter, docstring-derived named
@@ -327,6 +310,9 @@ def _make_command(api_cls: type, op: _Operation) -> click.Command:
327
310
  flag = f"--{opt_name.replace('_', '-')}"
328
311
  dp = op.params.get(name)
329
312
  help_text = _escape_help(dp.description) if dp else ""
313
+ if name != opt_name:
314
+ suffix = f"(SDK kwarg: {name})"
315
+ help_text = f"{help_text} {suffix}".strip() if help_text else suffix
330
316
  options.append(click.Option([flag], required=True, help=help_text))
331
317
 
332
318
  # Unified --workspace option. The env-var fallback only applies when the
@@ -420,18 +406,10 @@ def _make_command(api_cls: type, op: _Operation) -> click.Command:
420
406
  if all_items and max_items is not None:
421
407
  raise click.UsageError("--max-items cannot be combined with --all-items")
422
408
 
423
- # --max-items 0 makes no API call; skip session creation so we don't
424
- # briefly set Configuration.page_limit = 0 (derived from max_items)
425
- # on a session we immediately discard.
426
- if max_items == 0:
427
- return []
428
-
429
- effective_page_size = _resolve_effective_page_size(page_size, max_items)
409
+ want_iter = paginatable and (all_items or max_items is not None)
430
410
 
431
411
  if paginatable:
432
- session_ctx = AsanaSession.from_env(
433
- use_page_iterator=all_items, page_size=effective_page_size
434
- )
412
+ session_ctx = AsanaSession.from_env(use_page_iterator=want_iter, page_size=page_size)
435
413
  else:
436
414
  session_ctx = AsanaSession.from_env()
437
415
 
@@ -459,17 +437,24 @@ def _make_command(api_cls: type, op: _Operation) -> click.Command:
459
437
  call_args.append(kwargs.pop(_option_name(name)))
460
438
 
461
439
  method = getattr(api, op.method_name)
440
+ # ``--max-items N`` maps to the SDK's native ``item_limit`` kwarg
441
+ # on paginated methods: PageIterator caps the per-call ``limit``
442
+ # to ``min(page_limit, item_limit - count)`` and stops at exactly
443
+ # N items.
444
+ method_kwargs: dict[str, Any] = {}
462
445
  if paginatable and max_items is not None:
463
- return session.fetch_capped(method, *call_args, opts=opts, max_items=max_items)
464
- result = method(*call_args, opts) if op.has_opts else method(*call_args)
465
- # When --all-items is active the SDK returns a PageIterator
466
- # that lazily issues an HTTP request per page on iteration.
467
- # The formatter would otherwise iterate it after this ``with``
468
- # block exits — that is, after the debug redactor has been
469
- # uninstalled — leaking Authorization headers on every page
470
- # past the first. Consume it here so every page request lands
471
- # while the session (and its redactor) is still live.
472
- if paginatable and all_items:
446
+ method_kwargs["item_limit"] = max_items
447
+ result = (
448
+ method(*call_args, opts, **method_kwargs) if op.has_opts else method(*call_args)
449
+ )
450
+ # When the SDK returns a PageIterator it lazily issues an HTTP
451
+ # request per page on iteration. The formatter would otherwise
452
+ # iterate it after this ``with`` block exits — that is, after
453
+ # the debug redactor has been uninstalled — leaking Authorization
454
+ # headers on every page past the first. Consume it here so every
455
+ # page request lands while the session (and its redactor) is
456
+ # still live.
457
+ if want_iter:
473
458
  result = list(result)
474
459
  return result
475
460
 
@@ -286,7 +286,6 @@ class AsanaSession:
286
286
  # When --page-size is set, override the SDK default per-page size (100).
287
287
  if page_size is not None:
288
288
  config.page_limit = page_size
289
- self._page_size = page_size
290
289
 
291
290
  # Apply runtime values to Configuration
292
291
  if runtime.host:
@@ -382,54 +381,6 @@ class AsanaSession:
382
381
  sys.exit(1)
383
382
  return cls(token=token, use_page_iterator=use_page_iterator, page_size=page_size)
384
383
 
385
- def fetch_capped(
386
- self,
387
- api_method: Callable[..., Any],
388
- *args: Any,
389
- opts: dict[str, Any],
390
- max_items: int,
391
- ) -> list[Any]:
392
- """Fetch up to *max_items* items by manually walking ``next_page.offset``.
393
-
394
- The last request is automatically capped to the remaining count so we
395
- do not overfetch. Returns a flat list of items, matching the shape
396
- produced by ``--all-items``.
397
- """
398
- # Disable the SDK's PageIterator path for the duration of these calls.
399
- config = self._client.configuration
400
- original = config.return_page_iterator
401
- config.return_page_iterator = False
402
- try:
403
- page_size_default = self._page_size if self._page_size is not None else 100
404
- items: list[Any] = []
405
- opts = dict(opts) # don't mutate the caller's dict
406
- while len(items) < max_items:
407
- remaining = max_items - len(items)
408
- opts["limit"] = min(remaining, page_size_default)
409
- response = api_method(*args, opts)
410
- page_data = _extract(response, "data") or []
411
- items.extend(page_data)
412
- # Empty page with a non-empty offset would loop forever with zero progress.
413
- if not page_data:
414
- break
415
- next_page = _extract(response, "next_page")
416
- offset = _extract(next_page, "offset") if next_page else None
417
- if not offset:
418
- break
419
- opts["offset"] = offset
420
- return items[:max_items]
421
- finally:
422
- config.return_page_iterator = original
423
-
424
-
425
- def _extract(obj: Any, key: str) -> Any:
426
- """Read *key* from a SDK response that may be a dict or a typed model."""
427
- if obj is None:
428
- return None
429
- if isinstance(obj, dict):
430
- return obj.get(key)
431
- return getattr(obj, key, None)
432
-
433
384
 
434
385
  def resolve_workspace(
435
386
  explicit: str | None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asana-api-cli
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Command-line wrapper around the official Asana Python SDK
5
5
  Author-email: Masanao Izumo <asana@masanao.site>
6
6
  License-Expression: MIT
@@ -20,7 +20,6 @@ from asana_api_cli.cli import (
20
20
  _operations_for,
21
21
  _parse_params,
22
22
  _parse_summary,
23
- _resolve_effective_page_size,
24
23
  _snake,
25
24
  main,
26
25
  )
@@ -178,6 +177,13 @@ class TestBuiltCommands:
178
177
  # Path positional ``task_gid`` becomes ``--task`` (gid suffix stripped).
179
178
  assert "--task" in _option_flags(get_task_cmd)
180
179
 
180
+ def test_renamed_positional_help_shows_sdk_kwarg(self, get_task_cmd: click.Command) -> None:
181
+ # When ``task_gid`` is exposed as ``--task``, the original SDK kwarg
182
+ # name must appear in the help text so users can map the CLI flag back
183
+ # to the python-asana API.
184
+ task_param = next(p for p in get_task_cmd.params if "--task" in p.opts)
185
+ assert "task_gid" in (task_param.help or "")
186
+
181
187
  def test_get_tasks_pagination_options(self, get_tasks_cmd: click.Command) -> None:
182
188
  flags = _option_flags(get_tasks_cmd)
183
189
  assert "--all-items" in flags
@@ -212,45 +218,6 @@ class TestBuiltCommands:
212
218
  assert "--query" in flags
213
219
 
214
220
 
215
- # ---------------------------------------------------------------------------
216
- # Pagination size resolution
217
- # ---------------------------------------------------------------------------
218
-
219
-
220
- class TestEffectivePageSize:
221
- """``_resolve_effective_page_size`` decides the per-page ``limit`` value.
222
-
223
- The Asana API caps ``limit`` at 100, so we must never request more than
224
- 100 per page even when ``--max-items`` is larger.
225
- """
226
-
227
- def test_no_max_items_returns_page_size(self) -> None:
228
- assert _resolve_effective_page_size(None, None) is None
229
- assert _resolve_effective_page_size(50, None) == 50
230
-
231
- def test_max_items_below_default_shrinks(self) -> None:
232
- # max_items=5, page_size unset → request a single page of 5.
233
- assert _resolve_effective_page_size(None, 5) == 5
234
-
235
- def test_max_items_above_api_cap_keeps_default(self) -> None:
236
- # max_items=250, page_size unset → keep page_size None so fetch_capped
237
- # falls back to the 100-per-page default. Returning 250 would push the
238
- # API's limit parameter above its 100 ceiling and produce a 400.
239
- assert _resolve_effective_page_size(None, 250) is None
240
-
241
- def test_max_items_equal_to_cap_keeps_default(self) -> None:
242
- # 100 < 100 is False — no shrink, leave page_size None.
243
- assert _resolve_effective_page_size(None, 100) is None
244
-
245
- def test_explicit_page_size_smaller_than_max_items_kept(self) -> None:
246
- # User explicitly requested 50/page; auto-page through it.
247
- assert _resolve_effective_page_size(50, 250) == 50
248
-
249
- def test_explicit_page_size_larger_than_max_items_shrinks(self) -> None:
250
- # No point fetching 50 items just to throw 30 away.
251
- assert _resolve_effective_page_size(50, 20) == 20
252
-
253
-
254
221
  # ---------------------------------------------------------------------------
255
222
  # Root group integration
256
223
  # ---------------------------------------------------------------------------
@@ -3,11 +3,11 @@
3
3
  Each test builds a real command via ``_make_command`` against a real
4
4
  ``*Api`` class, replaces the underlying SDK method with a ``MagicMock``,
5
5
  drives the command through ``CliRunner``, and asserts on what reached the
6
- SDK (positional args, opts dict, call count).
6
+ SDK (positional args, opts dict, kwargs, call count).
7
7
 
8
8
  This catches bugs in the argument-plumbing layer that structural tests
9
- miss -- for example the ``--max-items > 100`` regression where the CLI
10
- forwarded ``limit=N`` to the SDK even though Asana caps ``limit`` at 100.
9
+ miss -- for example forgetting to forward ``--max-items`` as the SDK's
10
+ ``item_limit`` kwarg.
11
11
  """
12
12
 
13
13
  from __future__ import annotations
@@ -91,9 +91,8 @@ def _patch(
91
91
 
92
92
  ``MagicMock.call_args_list[i].args`` reflects the arguments the CLI
93
93
  passed in (without ``self``, since the stub strips it). Mutable args
94
- (the ``opts`` dict) are deep-copied before being recorded so that
95
- ``fetch_capped``'s in-place mutation across iterations does not collapse
96
- every recorded call into the dict's final state.
94
+ (the ``opts`` dict) are deep-copied before being recorded so each
95
+ call's snapshot survives any later in-place mutation.
97
96
  """
98
97
  api_cls = getattr(asana, api_cls_name)
99
98
  mock = MagicMock(
@@ -110,140 +109,103 @@ def _patch(
110
109
 
111
110
 
112
111
  # ---------------------------------------------------------------------------
113
- # Pagination: --max-items size handling (regression area)
112
+ # Pagination: --max-items forwards to the SDK's ``item_limit`` kwarg
114
113
  # ---------------------------------------------------------------------------
115
114
 
116
115
 
117
- class TestMaxItemsLimitArg:
118
- """``--max-items`` must never push ``opts["limit"]`` above the API cap (100)."""
116
+ class TestMaxItemsKwarg:
117
+ """``--max-items N`` is forwarded as ``item_limit=N`` to the SDK method.
119
118
 
120
- def test_max_items_250_walks_100_100_50(self, monkeypatch: pytest.MonkeyPatch) -> None:
121
- """Regression: previously this forwarded ``limit=250`` and got a 400."""
122
- cmd = _build_command("TasksApi", "get_tasks")
123
- responses = [
124
- _page([{"gid": str(i)} for i in range(0, 100)], offset="off1"),
125
- _page([{"gid": str(i)} for i in range(100, 200)], offset="off2"),
126
- _page([{"gid": str(i)} for i in range(200, 250)]),
127
- ]
128
- mock = _patch(monkeypatch, "TasksApi", "get_tasks", side_effect=responses)
129
-
130
- result = CliRunner().invoke(cmd, ["--max-items", "250"])
119
+ The SDK's ``PageIterator`` then caps each per-request ``limit`` to
120
+ ``min(page_limit, item_limit - count)`` and stops at exactly N items,
121
+ so the CLI does not need to walk pages itself.
122
+ """
131
123
 
132
- assert result.exit_code == 0, result.output
133
- assert mock.call_count == 3
134
- limits = [c.args[0]["limit"] for c in mock.call_args_list]
135
- assert limits == [100, 100, 50]
136
- # ``offset`` is threaded from each response into the next request.
137
- assert "offset" not in mock.call_args_list[0].args[0]
138
- assert mock.call_args_list[1].args[0]["offset"] == "off1"
139
- assert mock.call_args_list[2].args[0]["offset"] == "off2"
140
-
141
- def test_max_items_exactly_at_cap(self, monkeypatch: pytest.MonkeyPatch) -> None:
124
+ def test_max_items_passes_item_limit_kwarg(self, monkeypatch: pytest.MonkeyPatch) -> None:
142
125
  cmd = _build_command("TasksApi", "get_tasks")
143
126
  mock = _patch(
144
127
  monkeypatch,
145
128
  "TasksApi",
146
129
  "get_tasks",
147
- side_effect=[_page([{"gid": str(i)} for i in range(100)])],
130
+ return_value=iter([{"gid": str(i)} for i in range(250)]),
148
131
  )
149
- result = CliRunner().invoke(cmd, ["--max-items", "100"])
132
+ result = CliRunner().invoke(cmd, ["--max-items", "250"])
150
133
  assert result.exit_code == 0, result.output
151
134
  assert mock.call_count == 1
152
- assert mock.call_args_list[0].args[0]["limit"] == 100
135
+ assert mock.call_args_list[0].kwargs == {"item_limit": 250}
136
+ # CLI does not push ``limit`` into opts; that's the SDK's job.
137
+ assert "limit" not in mock.call_args_list[0].args[0]
138
+ assert len(json.loads(result.output)) == 250
153
139
 
154
- def test_max_items_below_cap_one_request(self, monkeypatch: pytest.MonkeyPatch) -> None:
140
+ def test_max_items_zero_passes_item_limit_zero(self, monkeypatch: pytest.MonkeyPatch) -> None:
141
+ """``--max-items 0`` still constructs a session and calls the SDK; the
142
+ SDK's PageIterator short-circuits and makes no HTTP request."""
155
143
  cmd = _build_command("TasksApi", "get_tasks")
156
144
  mock = _patch(
157
145
  monkeypatch,
158
146
  "TasksApi",
159
147
  "get_tasks",
160
- side_effect=[_page([{"gid": str(i)} for i in range(5)])],
148
+ return_value=iter([]),
161
149
  )
162
- result = CliRunner().invoke(cmd, ["--max-items", "5"])
163
- assert result.exit_code == 0, result.output
164
- assert mock.call_count == 1
165
- assert mock.call_args_list[0].args[0]["limit"] == 5
166
-
167
- def test_max_items_zero_makes_no_api_call(self, monkeypatch: pytest.MonkeyPatch) -> None:
168
- """``--max-items 0`` returns ``[]`` without ever calling the SDK."""
169
- cmd = _build_command("TasksApi", "get_tasks")
170
- mock = _patch(monkeypatch, "TasksApi", "get_tasks", return_value=_page([]))
171
150
  result = CliRunner().invoke(cmd, ["--max-items", "0"])
172
151
  assert result.exit_code == 0, result.output
173
- assert mock.call_count == 0
174
- assert json.loads(result.output) == []
175
-
176
-
177
- # ---------------------------------------------------------------------------
178
- # Pagination: termination conditions
179
- # ---------------------------------------------------------------------------
180
-
181
-
182
- class TestMaxItemsTermination:
183
- """``fetch_capped`` must terminate even when the server cuts pagination short."""
184
-
185
- def test_stops_when_offset_missing(self, monkeypatch: pytest.MonkeyPatch) -> None:
186
- """No ``next_page.offset`` -> stop, even if ``--max-items`` was larger."""
187
- cmd = _build_command("TasksApi", "get_tasks")
188
- mock = _patch(
189
- monkeypatch,
190
- "TasksApi",
191
- "get_tasks",
192
- side_effect=[_page([{"gid": "1"}], offset=None)],
193
- )
194
- result = CliRunner().invoke(cmd, ["--max-items", "500"])
195
- assert result.exit_code == 0, result.output
196
152
  assert mock.call_count == 1
197
- assert json.loads(result.output) == [{"gid": "1"}]
153
+ assert mock.call_args_list[0].kwargs == {"item_limit": 0}
154
+ assert json.loads(result.output) == []
198
155
 
199
- def test_stops_on_empty_page_with_phantom_offset(self, monkeypatch: pytest.MonkeyPatch) -> None:
200
- """Empty page with a non-empty offset must not loop forever (zero-progress guard)."""
156
+ def test_max_items_5_passes_item_limit_5(self, monkeypatch: pytest.MonkeyPatch) -> None:
201
157
  cmd = _build_command("TasksApi", "get_tasks")
202
158
  mock = _patch(
203
159
  monkeypatch,
204
160
  "TasksApi",
205
161
  "get_tasks",
206
- side_effect=[_page([], offset="phantom")],
162
+ return_value=iter([{"gid": str(i)} for i in range(5)]),
207
163
  )
208
- result = CliRunner().invoke(cmd, ["--max-items", "500"])
164
+ result = CliRunner().invoke(cmd, ["--max-items", "5"])
209
165
  assert result.exit_code == 0, result.output
210
- assert mock.call_count == 1
166
+ assert mock.call_args_list[0].kwargs == {"item_limit": 5}
211
167
 
212
168
 
213
169
  # ---------------------------------------------------------------------------
214
- # Pagination: --page-size interaction with --max-items
170
+ # Pagination: --page-size feeds Configuration.page_limit
215
171
  # ---------------------------------------------------------------------------
216
172
 
217
173
 
218
- class TestPageSizeWithMaxItems:
219
- """``--page-size`` controls per-request size; ``--max-items`` caps the total."""
174
+ class TestPageSizeKwarg:
175
+ """``--page-size N`` sets ``Configuration.page_limit = N`` for the SDK.
220
176
 
221
- def test_page_size_smaller_than_max_items_used_as_limit(
222
- self, monkeypatch: pytest.MonkeyPatch
223
- ) -> None:
177
+ The SDK reads ``page_limit`` to populate ``query_params['limit']`` when
178
+ the caller did not. We assert by snapshotting the configuration at the
179
+ moment the SDK method is invoked.
180
+ """
181
+
182
+ def _capture_page_limit_on_call(
183
+ self, monkeypatch: pytest.MonkeyPatch, return_value: Any
184
+ ) -> list[int]:
185
+ captured: list[int] = []
186
+
187
+ def patched_get_tasks(self_api: Any, opts: Any, **kwargs: Any) -> Any:
188
+ captured.append(self_api.api_client.configuration.page_limit)
189
+ return return_value
190
+
191
+ monkeypatch.setattr(asana.TasksApi, "get_tasks", patched_get_tasks)
192
+ return captured
193
+
194
+ def test_page_size_sets_page_limit(self, monkeypatch: pytest.MonkeyPatch) -> None:
224
195
  cmd = _build_command("TasksApi", "get_tasks")
225
- responses = [
226
- _page([{"gid": str(i)} for i in range(0, 50)], offset="o1"),
227
- _page([{"gid": str(i)} for i in range(50, 100)], offset="o2"),
228
- _page([{"gid": str(i)} for i in range(100, 120)]),
229
- ]
230
- mock = _patch(monkeypatch, "TasksApi", "get_tasks", side_effect=responses)
231
- result = CliRunner().invoke(cmd, ["--page-size", "50", "--max-items", "120"])
196
+ captured = self._capture_page_limit_on_call(monkeypatch, return_value=iter([{"gid": "1"}]))
197
+ result = CliRunner().invoke(cmd, ["--page-size", "50", "--max-items", "10"])
232
198
  assert result.exit_code == 0, result.output
233
- assert [c.args[0]["limit"] for c in mock.call_args_list] == [50, 50, 20]
199
+ assert captured == [50]
234
200
 
235
- def test_page_size_larger_than_max_items_shrinks(self, monkeypatch: pytest.MonkeyPatch) -> None:
201
+ def test_no_page_size_keeps_sdk_default(self, monkeypatch: pytest.MonkeyPatch) -> None:
202
+ """Default ``page_limit`` is 100 (the SDK default) when ``--page-size``
203
+ is not passed."""
236
204
  cmd = _build_command("TasksApi", "get_tasks")
237
- mock = _patch(
238
- monkeypatch,
239
- "TasksApi",
240
- "get_tasks",
241
- side_effect=[_page([{"gid": str(i)} for i in range(20)])],
242
- )
243
- result = CliRunner().invoke(cmd, ["--page-size", "50", "--max-items", "20"])
205
+ captured = self._capture_page_limit_on_call(monkeypatch, return_value=_page([]))
206
+ result = CliRunner().invoke(cmd, [])
244
207
  assert result.exit_code == 0, result.output
245
- assert mock.call_count == 1
246
- assert mock.call_args_list[0].args[0]["limit"] == 20
208
+ assert captured == [100]
247
209
 
248
210
 
249
211
  # ---------------------------------------------------------------------------
File without changes
File without changes
File without changes