asana-api-cli 3.1.0__tar.gz → 3.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-3.1.0/src/asana_api_cli.egg-info → asana_api_cli-3.1.1}/PKG-INFO +1 -1
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/pyproject.toml +7 -7
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/cli.py +4 -4
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/click_ext.py +2 -2
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/session.py +1 -1
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/structured_arg.py +2 -2
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1/src/asana_api_cli.egg-info}/PKG-INFO +1 -1
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_cli_invocation.py +10 -3
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_sdk_boilerplate.py +17 -9
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_session.py +1 -1
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_structured_arg.py +2 -2
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/LICENSE +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/README.md +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/setup.cfg +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/__init__.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/formatter.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/redactor.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli/version.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli.egg-info/SOURCES.txt +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli.egg-info/dependency_links.txt +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli.egg-info/entry_points.txt +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli.egg-info/requires.txt +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/src/asana_api_cli.egg-info/top_level.txt +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_cli.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_cli_surface.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_click_ext.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_formatter.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_py310_compat.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_redactor.py +0 -0
- {asana_api_cli-3.1.0 → asana_api_cli-3.1.1}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "asana-api-cli"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.1"
|
|
4
4
|
description = "Command-line wrapper around the official Asana Python SDK"
|
|
5
5
|
authors = [{name = "Masanao Izumo"}]
|
|
6
6
|
readme = "README.md"
|
|
@@ -35,13 +35,13 @@ asana-api = "asana_api_cli.cli:main"
|
|
|
35
35
|
|
|
36
36
|
[dependency-groups]
|
|
37
37
|
dev = [
|
|
38
|
-
"ruff>=0.
|
|
39
|
-
"basedpyright>=1.
|
|
38
|
+
"ruff>=0.15",
|
|
39
|
+
"basedpyright>=1.39",
|
|
40
40
|
"pytest>=9,<10",
|
|
41
|
-
"build>=1,<2",
|
|
42
|
-
"twine>=6,<7",
|
|
43
|
-
"vermin>=1.
|
|
44
|
-
"vcrpy>=
|
|
41
|
+
"build>=1.5,<2",
|
|
42
|
+
"twine>=6.2,<7",
|
|
43
|
+
"vermin>=1.8,<2",
|
|
44
|
+
"vcrpy>=8",
|
|
45
45
|
"pytest-recording>=0.13",
|
|
46
46
|
]
|
|
47
47
|
|
|
@@ -350,14 +350,14 @@ def _escape_help(text: str) -> str:
|
|
|
350
350
|
# asana-api label uses parentheses. Six kinds cover the SDK input structure:
|
|
351
351
|
#
|
|
352
352
|
# (Configuration: <name>) set on asana.Configuration (global flags)
|
|
353
|
-
# (ApiClient: <name>) set on the ApiClient instance (user_agent,
|
|
353
|
+
# (ApiClient: <name>) set on the ApiClient instance (user_agent, set_default_header)
|
|
354
354
|
# (args: <name>) positional method argument (body / path GID / workspace_gid)
|
|
355
355
|
# (opts: <name>) entry in the method ``opts`` dict (docstring :param)
|
|
356
356
|
# (kwargs: <name>) boilerplate **kwargs every method accepts (all_params)
|
|
357
357
|
# (asana-api: extension) no SDK counterpart (CLI-only)
|
|
358
358
|
#
|
|
359
359
|
# Configuration globals and the two ApiClient-instance globals (--user-agent /
|
|
360
|
-
# --default-header) carry the literal by hand in both ``main`` and
|
|
360
|
+
# --set-default-header) carry the literal by hand in both ``main`` and
|
|
361
361
|
# ``_make_global_option_params`` (kept byte-identical between cli.py and
|
|
362
362
|
# click_ext.py by ``test_click_ext.TestHelpTextSync``); the CLI-only formatter
|
|
363
363
|
# flags (``--output`` / ``--query`` / ``--csv-bom`` and the error-path twins
|
|
@@ -1210,7 +1210,7 @@ def _retry_strategy_option(f: Any) -> Any:
|
|
|
1210
1210
|
help=("Override the User-Agent header the SDK sends on every request. (ApiClient: user_agent)"),
|
|
1211
1211
|
)
|
|
1212
1212
|
@click.option(
|
|
1213
|
-
"--default-header",
|
|
1213
|
+
"--set-default-header",
|
|
1214
1214
|
"default_headers",
|
|
1215
1215
|
multiple=True,
|
|
1216
1216
|
callback=default_header_callback,
|
|
@@ -1218,7 +1218,7 @@ def _retry_strategy_option(f: Any) -> Any:
|
|
|
1218
1218
|
"Add an HTTP header sent on every request, given as NAME=VALUE; "
|
|
1219
1219
|
"repeatable. Unlike per-call --header-params it applies to all "
|
|
1220
1220
|
"calls. Not redacted in --debug output — see SECURITY.md. "
|
|
1221
|
-
"(ApiClient:
|
|
1221
|
+
"(ApiClient: set_default_header)"
|
|
1222
1222
|
),
|
|
1223
1223
|
)
|
|
1224
1224
|
@_retry_strategy_option
|
|
@@ -166,14 +166,14 @@ def _make_global_option_params() -> list[click.Option]:
|
|
|
166
166
|
),
|
|
167
167
|
),
|
|
168
168
|
click.Option(
|
|
169
|
-
["--default-header", "default_headers"],
|
|
169
|
+
["--set-default-header", "default_headers"],
|
|
170
170
|
multiple=True,
|
|
171
171
|
callback=default_header_callback,
|
|
172
172
|
help=(
|
|
173
173
|
"Add an HTTP header sent on every request, given as NAME=VALUE; "
|
|
174
174
|
"repeatable. Unlike per-call --header-params it applies to all "
|
|
175
175
|
"calls. Not redacted in --debug output — see SECURITY.md. "
|
|
176
|
-
"(ApiClient:
|
|
176
|
+
"(ApiClient: set_default_header)"
|
|
177
177
|
),
|
|
178
178
|
),
|
|
179
179
|
*(
|
|
@@ -222,7 +222,7 @@ class AsanaSession:
|
|
|
222
222
|
|
|
223
223
|
# ApiClient-instance settings (not Configuration knobs) applied after
|
|
224
224
|
# construction. ``default_headers`` first, then ``user_agent`` last, so
|
|
225
|
-
# the dedicated ``--user-agent`` wins over a ``--default-header`` that
|
|
225
|
+
# the dedicated ``--user-agent`` wins over a ``--set-default-header`` that
|
|
226
226
|
# also targets ``User-Agent`` (both write ``default_headers['User-Agent']``).
|
|
227
227
|
if runtime.default_headers:
|
|
228
228
|
for name, value in runtime.default_headers.items():
|
|
@@ -24,7 +24,7 @@ insensitive). ``1`` / ``0`` are intentionally rejected so int and bool
|
|
|
24
24
|
fields cannot be confused by readers of the command line.
|
|
25
25
|
|
|
26
26
|
The module also hosts :func:`default_header_callback`, a separate
|
|
27
|
-
``NAME=VALUE``-per-occurrence parser for the repeatable ``--default-header``
|
|
27
|
+
``NAME=VALUE``-per-occurrence parser for the repeatable ``--set-default-header``
|
|
28
28
|
global. It is deliberately not the hybrid parser above: header values often
|
|
29
29
|
contain commas, which the ``key=value,key=value`` shorthand would mis-split.
|
|
30
30
|
"""
|
|
@@ -210,7 +210,7 @@ def click_callback(
|
|
|
210
210
|
def default_header_callback(
|
|
211
211
|
ctx: click.Context, param: click.Parameter, value: tuple[str, ...]
|
|
212
212
|
) -> dict[str, str] | None:
|
|
213
|
-
"""Click ``callback`` for a repeatable ``--default-header NAME=VALUE`` option.
|
|
213
|
+
"""Click ``callback`` for a repeatable ``--set-default-header NAME=VALUE`` option.
|
|
214
214
|
|
|
215
215
|
``multiple=True`` hands the callback a tuple of raw ``NAME=VALUE`` tokens
|
|
216
216
|
(empty when the flag was not given). Returns ``None`` for "not given" so the
|
|
@@ -510,7 +510,7 @@ class TestRetryStrategyReachesSession:
|
|
|
510
510
|
|
|
511
511
|
|
|
512
512
|
class TestHttpHeaderGlobalsReachClient:
|
|
513
|
-
"""``--user-agent`` and ``--default-header`` are ApiClient-instance globals;
|
|
513
|
+
"""``--user-agent`` and ``--set-default-header`` are ApiClient-instance globals;
|
|
514
514
|
they must reach the ``ApiClient`` that issues the request, and (like every
|
|
515
515
|
global) be accepted at the leaf-command level."""
|
|
516
516
|
|
|
@@ -538,7 +538,14 @@ class TestHttpHeaderGlobalsReachClient:
|
|
|
538
538
|
monkeypatch.setattr(asana.TasksApi, "get_task", patched)
|
|
539
539
|
result = make_runner().invoke(
|
|
540
540
|
cmd,
|
|
541
|
-
[
|
|
541
|
+
[
|
|
542
|
+
"--set-default-header",
|
|
543
|
+
"X-Foo=bar",
|
|
544
|
+
"--set-default-header",
|
|
545
|
+
"X-Baz=qux",
|
|
546
|
+
"--task",
|
|
547
|
+
"T",
|
|
548
|
+
],
|
|
542
549
|
)
|
|
543
550
|
assert result.exit_code == 0, full_output(result)
|
|
544
551
|
assert captured[0]["X-Foo"] == "bar"
|
|
@@ -547,7 +554,7 @@ class TestHttpHeaderGlobalsReachClient:
|
|
|
547
554
|
def test_malformed_default_header_is_usage_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
548
555
|
cmd = _build_command("TasksApi", "get_task")
|
|
549
556
|
_patch(monkeypatch, "TasksApi", "get_task", return_value={"data": {}})
|
|
550
|
-
result = make_runner().invoke(cmd, ["--default-header", "noequals", "--task", "T"])
|
|
557
|
+
result = make_runner().invoke(cmd, ["--set-default-header", "noequals", "--task", "T"])
|
|
551
558
|
assert result.exit_code == 2, full_output(result)
|
|
552
559
|
assert "NAME=VALUE" in full_output(result)
|
|
553
560
|
|
|
@@ -53,8 +53,8 @@ EXPECTED_ALL_PARAMS: frozenset[str] = frozenset(
|
|
|
53
53
|
}
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
56
|
+
# Settable attributes the CLI knows about on a default ``asana.Configuration()``.
|
|
57
|
+
# Not every one is a CLI flag: object-/callable-typed members (``logger*``,
|
|
58
58
|
# ``refresh_api_key_hook``) cannot be flags, and the inert auth fields
|
|
59
59
|
# (``username`` / ``password`` / ``api_key`` / ``api_key_prefix``) are
|
|
60
60
|
# deliberately NOT exposed — Asana auth is Bearer-token only, so they are
|
|
@@ -62,6 +62,14 @@ EXPECTED_ALL_PARAMS: frozenset[str] = frozenset(
|
|
|
62
62
|
# set because the SDK's Configuration still declares them; this guard tracks the
|
|
63
63
|
# SDK surface, not the CLI's. ``logger_format`` / ``logger_file`` reach the
|
|
64
64
|
# ``logger*`` members via @property setters.
|
|
65
|
+
#
|
|
66
|
+
# This is the union across the supported asana range (>=5.0.2), pinned at the
|
|
67
|
+
# latest. ``test_no_unknown_configuration_settable_attrs`` checks only one
|
|
68
|
+
# direction — that the installed SDK exposes nothing OUTSIDE this set, since a
|
|
69
|
+
# *new* attribute is the actionable drift (it likely needs a flag). Older SDKs
|
|
70
|
+
# initialize fewer attributes (``access_token`` arrived in 5.0.6,
|
|
71
|
+
# ``retry_strategy`` in 5.1.0); those absences are tolerated so the guard passes
|
|
72
|
+
# on the floor as well as the pinned version.
|
|
65
73
|
EXPECTED_CONFIGURATION_ATTRS: frozenset[str] = frozenset(
|
|
66
74
|
{
|
|
67
75
|
"access_token",
|
|
@@ -191,15 +199,15 @@ def test_all_methods_share_expected_all_params() -> None:
|
|
|
191
199
|
)
|
|
192
200
|
|
|
193
201
|
|
|
194
|
-
def
|
|
202
|
+
def test_no_unknown_configuration_settable_attrs() -> None:
|
|
195
203
|
got = frozenset(k for k in vars(asana.Configuration()) if not k.startswith("_"))
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
f"
|
|
204
|
+
unknown = got - EXPECTED_CONFIGURATION_ATTRS
|
|
205
|
+
assert not unknown, (
|
|
206
|
+
"asana.Configuration exposes settable attributes the CLI does not know "
|
|
207
|
+
f"about: {sorted(unknown)}\n"
|
|
200
208
|
"A newly settable property likely needs a global flag + "
|
|
201
|
-
"(Configuration: <name>) label in cli.py / click_ext.py
|
|
202
|
-
"See docs/cli-sdk-mapping.md."
|
|
209
|
+
"(Configuration: <name>) label in cli.py / click_ext.py, then adding it "
|
|
210
|
+
"to EXPECTED_CONFIGURATION_ATTRS. See docs/cli-sdk-mapping.md."
|
|
203
211
|
)
|
|
204
212
|
|
|
205
213
|
|
|
@@ -330,7 +330,7 @@ class TestAsanaSessionPaginationKwargs:
|
|
|
330
330
|
|
|
331
331
|
|
|
332
332
|
# ---------------------------------------------------------------------------
|
|
333
|
-
# ApiClient-instance settings (--user-agent / --default-header)
|
|
333
|
+
# ApiClient-instance settings (--user-agent / --set-default-header)
|
|
334
334
|
# ---------------------------------------------------------------------------
|
|
335
335
|
|
|
336
336
|
|
|
@@ -224,13 +224,13 @@ class TestRetryFieldSchema:
|
|
|
224
224
|
def _default_headers(*tokens: str) -> dict[str, str] | None:
|
|
225
225
|
"""Run ``default_header_callback`` with a real (throwaway) Click context,
|
|
226
226
|
mirroring how ``multiple=True`` hands it a tuple of raw tokens."""
|
|
227
|
-
param = click.Option(["--default-header"], multiple=True)
|
|
227
|
+
param = click.Option(["--set-default-header"], multiple=True)
|
|
228
228
|
ctx = click.Context(click.Command("test"))
|
|
229
229
|
return default_header_callback(ctx, param, tokens)
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
class TestDefaultHeaderCallback:
|
|
233
|
-
"""``--default-header NAME=VALUE`` (repeatable) parser."""
|
|
233
|
+
"""``--set-default-header NAME=VALUE`` (repeatable) parser."""
|
|
234
234
|
|
|
235
235
|
def test_not_given_returns_none(self) -> None:
|
|
236
236
|
# Matches the unset sentinel the other globals use.
|
|
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
|
|
File without changes
|