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.
- {asana_api_cli-2.1.0/src/asana_api_cli.egg-info → asana_api_cli-2.1.1}/PKG-INFO +1 -1
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/pyproject.toml +1 -1
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/cli.py +22 -37
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/session.py +0 -49
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1/src/asana_api_cli.egg-info}/PKG-INFO +1 -1
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_cli.py +7 -40
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_cli_invocation.py +59 -97
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/LICENSE +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/README.md +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/setup.cfg +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/__init__.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/click_ext.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/formatter.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli/version.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/SOURCES.txt +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/dependency_links.txt +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/entry_points.txt +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/requires.txt +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/src/asana_api_cli.egg-info/top_level.txt +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_cli_surface.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_click_ext.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_formatter.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_py310_compat.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_session.py +0 -0
- {asana_api_cli-2.1.0 → asana_api_cli-2.1.1}/tests/test_version.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
464
|
-
result =
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
#
|
|
468
|
-
#
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
|
|
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,
|
|
@@ -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
|
|
10
|
-
|
|
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
|
|
95
|
-
|
|
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
|
|
112
|
+
# Pagination: --max-items forwards to the SDK's ``item_limit`` kwarg
|
|
114
113
|
# ---------------------------------------------------------------------------
|
|
115
114
|
|
|
116
115
|
|
|
117
|
-
class
|
|
118
|
-
"""``--max-items``
|
|
116
|
+
class TestMaxItemsKwarg:
|
|
117
|
+
"""``--max-items N`` is forwarded as ``item_limit=N`` to the SDK method.
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
+
return_value=iter([{"gid": str(i)} for i in range(250)]),
|
|
148
131
|
)
|
|
149
|
-
result = CliRunner().invoke(cmd, ["--max-items", "
|
|
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].
|
|
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
|
|
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
|
-
|
|
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
|
|
153
|
+
assert mock.call_args_list[0].kwargs == {"item_limit": 0}
|
|
154
|
+
assert json.loads(result.output) == []
|
|
198
155
|
|
|
199
|
-
def
|
|
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
|
-
|
|
162
|
+
return_value=iter([{"gid": str(i)} for i in range(5)]),
|
|
207
163
|
)
|
|
208
|
-
result = CliRunner().invoke(cmd, ["--max-items", "
|
|
164
|
+
result = CliRunner().invoke(cmd, ["--max-items", "5"])
|
|
209
165
|
assert result.exit_code == 0, result.output
|
|
210
|
-
assert mock.
|
|
166
|
+
assert mock.call_args_list[0].kwargs == {"item_limit": 5}
|
|
211
167
|
|
|
212
168
|
|
|
213
169
|
# ---------------------------------------------------------------------------
|
|
214
|
-
# Pagination: --page-size
|
|
170
|
+
# Pagination: --page-size feeds Configuration.page_limit
|
|
215
171
|
# ---------------------------------------------------------------------------
|
|
216
172
|
|
|
217
173
|
|
|
218
|
-
class
|
|
219
|
-
"""``--page-size``
|
|
174
|
+
class TestPageSizeKwarg:
|
|
175
|
+
"""``--page-size N`` sets ``Configuration.page_limit = N`` for the SDK.
|
|
220
176
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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
|
|
199
|
+
assert captured == [50]
|
|
234
200
|
|
|
235
|
-
def
|
|
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
|
-
|
|
238
|
-
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|