asana-api-cli 3.2.0__tar.gz → 3.3.0__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 (36) hide show
  1. {asana_api_cli-3.2.0/src/asana_api_cli.egg-info → asana_api_cli-3.3.0}/PKG-INFO +4 -2
  2. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/README.md +3 -1
  3. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/pyproject.toml +1 -1
  4. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/cli.py +12 -0
  5. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/click_ext.py +18 -5
  6. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/multibyte_filename.py +1 -1
  7. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0/src/asana_api_cli.egg-info}/PKG-INFO +4 -2
  8. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_cli_surface.py +1 -1
  9. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_click_ext.py +87 -4
  10. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_generate_python_snapshots.py +1 -1
  11. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_sdk_boilerplate.py +22 -120
  12. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/LICENSE +0 -0
  13. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/setup.cfg +0 -0
  14. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/__init__.py +0 -0
  15. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/codegen.py +0 -0
  16. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/formatter.py +0 -0
  17. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/redactor.py +0 -0
  18. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/session.py +0 -0
  19. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/structured_arg.py +0 -0
  20. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli/version.py +0 -0
  21. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli.egg-info/SOURCES.txt +0 -0
  22. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli.egg-info/dependency_links.txt +0 -0
  23. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli.egg-info/entry_points.txt +0 -0
  24. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli.egg-info/requires.txt +0 -0
  25. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/src/asana_api_cli.egg-info/top_level.txt +0 -0
  26. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_cli.py +0 -0
  27. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_cli_invocation.py +0 -0
  28. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_codegen.py +0 -0
  29. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_formatter.py +0 -0
  30. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_generate_python.py +0 -0
  31. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_multibyte_filename.py +0 -0
  32. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_py310_compat.py +0 -0
  33. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_redactor.py +0 -0
  34. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_session.py +0 -0
  35. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_structured_arg.py +0 -0
  36. {asana_api_cli-3.2.0 → asana_api_cli-3.3.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asana-api-cli
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Command-line wrapper around the official Asana Python SDK
5
5
  Author: Masanao Izumo
6
6
  License-Expression: MIT
@@ -135,7 +135,9 @@ automatically.
135
135
 
136
136
  The token can be issued from the
137
137
  [Asana Developer Console](https://app.asana.com/0/developer-console).
138
- No token is needed for `--help` or argument validation errors.
138
+ No token is needed for `--help` or command-line parsing errors. (A `--query`
139
+ jq filter is validated against the response, so it surfaces errors only after
140
+ the API call — which does need a token.)
139
141
 
140
142
  ```bash
141
143
  export ASANA_ACCESS_TOKEN="2/12345..."
@@ -107,7 +107,9 @@ automatically.
107
107
 
108
108
  The token can be issued from the
109
109
  [Asana Developer Console](https://app.asana.com/0/developer-console).
110
- No token is needed for `--help` or argument validation errors.
110
+ No token is needed for `--help` or command-line parsing errors. (A `--query`
111
+ jq filter is validated against the response, so it surfaces errors only after
112
+ the API call — which does need a token.)
111
113
 
112
114
  ```bash
113
115
  export ASANA_ACCESS_TOKEN="2/12345..."
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asana-api-cli"
3
- version = "3.2.0"
3
+ version = "3.3.0"
4
4
  description = "Command-line wrapper around the official Asana Python SDK"
5
5
  authors = [{name = "Masanao Izumo"}]
6
6
  readme = "README.md"
@@ -224,6 +224,7 @@ def _humanize_class_name(name: str) -> str:
224
224
  # a release blocker.
225
225
  _GROUP_DESCRIPTIONS: dict[str, str] = {
226
226
  "AccessRequests": "Manage private-object access requests",
227
+ "Agents": "Read workspace AI agents (AI Teammates)",
227
228
  "Allocations": "Manage user allocations across projects",
228
229
  "Attachments": "Upload, list, and remove file attachments",
229
230
  "AuditLogAPI": "Read domain audit log events",
@@ -238,6 +239,7 @@ _GROUP_DESCRIPTIONS: dict[str, str] = {
238
239
  "Goals": "Manage organizational goals and metrics",
239
240
  "Jobs": "Check status of async background jobs",
240
241
  "Memberships": "Manage memberships across object types",
242
+ "OooEntries": "Manage out-of-office (OOO) entries",
241
243
  "OrganizationExports": "Trigger and download org-wide exports",
242
244
  "PortfolioMemberships": "Read who has access to portfolios",
243
245
  "Portfolios": "Manage portfolios (project collections)",
@@ -1383,6 +1385,16 @@ def introspect_to_manifest() -> dict[str, Any]:
1383
1385
  "positional": list(op.positional),
1384
1386
  "has_opts": op.has_opts,
1385
1387
  "paginatable": op.paginatable,
1388
+ # ``returns_iterator`` (array response → lazy iterator) and
1389
+ # ``does_upload`` (multipart file upload) pin the two runtime
1390
+ # classifiers that gate pagination materialization and the
1391
+ # ``--multibyte-filenames`` flag. Captured here so an SDK bump
1392
+ # that adds/removes such an endpoint surfaces in this fixture's
1393
+ # diff (the regen step), instead of in a hand-maintained set.
1394
+ # ``test_sdk_boilerplate.py`` still proves each classifier
1395
+ # matches the SDK source on the installed version.
1396
+ "returns_iterator": op.returns_iterator,
1397
+ "does_upload": op.does_upload,
1386
1398
  "params": [
1387
1399
  {
1388
1400
  "name": p.name,
@@ -45,7 +45,7 @@ from asana_api_cli.structured_arg import (
45
45
  # code path is naturally dead when the flag is hidden. We instantiate
46
46
  # ``Configuration()`` because ``retry_strategy`` is an instance attribute
47
47
  # set in ``__init__`` — checking ``hasattr`` on the class itself returns
48
- # False even on 5.2.4.
48
+ # False even on 5.2.5.
49
49
  _SDK_HAS_RETRY_STRATEGY: bool = hasattr(asana.Configuration(), "retry_strategy")
50
50
 
51
51
 
@@ -342,12 +342,22 @@ def _apply_global_to_runtime(name: str, value: Any) -> None:
342
342
  option declarations and the dataclass fields are authored together, and the
343
343
  inventory tests in ``test_click_ext.py`` (``TestGlobalOptionNamesInventory``)
344
344
  pin the ``GLOBAL_OPTION_NAMES`` set against ``_Runtime``'s fields — so a
345
- direct ``setattr`` suffices. Every option, ``access_token`` included,
346
- follows the same last-wins rule: the command-line value from the deepest
347
- level reached overwrites whatever an earlier level wrote. An explicit empty
348
- ``--access-token`` therefore clears a value set earlier, and
345
+ direct ``setattr`` suffices. Every *scalar* option, ``access_token``
346
+ included, follows the same last-wins rule: the command-line value from the
347
+ deepest level reached overwrites whatever an earlier level wrote. An
348
+ explicit empty ``--access-token`` therefore clears a value set earlier, and
349
349
  ``AsanaSession.from_env`` then falls back to ``$ASANA_ACCESS_TOKEN``.
350
350
 
351
+ ``default_headers`` is the one exception, because ``--set-default-header`` is
352
+ the only repeatable (accumulative) global: ``multiple=True`` already merges
353
+ every occurrence *at one level* into a single dict, so to honor "the later
354
+ occurrence wins when repeated" (``usage.md``) across levels too, values from
355
+ different levels are merged per-header rather than replaced wholesale. A
356
+ header set on the root group thus survives another ``--set-default-header``
357
+ given at the leaf, and a deeper level's value wins on a key collision. The
358
+ callback (:func:`structured_arg.default_header_callback`) yields ``None``
359
+ when the flag is absent, so only non-empty dicts ever reach this branch.
360
+
351
361
  The caller, :func:`_consume_global_options`, only ever passes names drawn
352
362
  from ``GLOBAL_OPTION_NAMES`` and only when the parameter source is
353
363
  ``ParameterSource.COMMANDLINE``. This is the single application path for
@@ -356,6 +366,9 @@ def _apply_global_to_runtime(name: str, value: Any) -> None:
356
366
  tri-state toggles' (``verify_ssl`` / ``assert_hostname`` /
357
367
  ``return_page_iterator``) ``None`` default never reaches here.
358
368
  """
369
+ if name == "default_headers":
370
+ runtime.default_headers = {**(runtime.default_headers or {}), **value}
371
+ return
359
372
  setattr(runtime, name, value)
360
373
 
361
374
 
@@ -16,7 +16,7 @@ from urllib3.fields import RequestField
16
16
  class MultibyteFilenameSupport:
17
17
  """Make multipart uploads round-trip filenames with non-ASCII characters.
18
18
 
19
- In ``python-asana`` 5.2.4 (the latest version checked, and likely later
19
+ In ``python-asana`` 5.2.5 (the latest version checked, and likely later
20
20
  ones too), uploading a file whose name has characters outside ASCII
21
21
  stores a garbled (mojibake) name on Asana: the SDK's multipart encoder
22
22
  emits only ``filename="..."`` and omits the RFC 5987 ``filename*=``
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asana-api-cli
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Command-line wrapper around the official Asana Python SDK
5
5
  Author: Masanao Izumo
6
6
  License-Expression: MIT
@@ -135,7 +135,9 @@ automatically.
135
135
 
136
136
  The token can be issued from the
137
137
  [Asana Developer Console](https://app.asana.com/0/developer-console).
138
- No token is needed for `--help` or argument validation errors.
138
+ No token is needed for `--help` or command-line parsing errors. (A `--query`
139
+ jq filter is validated against the response, so it surfaces errors only after
140
+ the API call — which does need a token.)
139
141
 
140
142
  ```bash
141
143
  export ASANA_ACCESS_TOKEN="2/12345..."
@@ -34,7 +34,7 @@ FIXTURE = Path(__file__).parent / "fixtures" / "cli_surface.json"
34
34
  # to diverge. Skip the snapshot tests when the SDK doesn't match rather
35
35
  # than failing — the surface drift on older / newer SDKs is informational,
36
36
  # not a regression to gate against.
37
- _SNAPSHOT_ASANA_VERSION = "5.2.4"
37
+ _SNAPSHOT_ASANA_VERSION = "5.2.5"
38
38
  _INSTALLED_ASANA_VERSION = version("asana")
39
39
 
40
40
  pytestmark = pytest.mark.skipif(
@@ -68,6 +68,35 @@ def _build_cli() -> click.Group:
68
68
  return root
69
69
 
70
70
 
71
+ def _build_cli_echoing_default_headers() -> click.Group:
72
+ """A 3-level CLI (root → sub → leaf) like :func:`_build_cli` whose leaf echoes
73
+ the accumulated ``runtime.default_headers``.
74
+
75
+ Used to drive the *real* ``--set-default-header`` global through Click
76
+ parsing at more than one level, so ``_consume_global_options`` and
77
+ ``_apply_global_to_runtime`` are exercised end-to-end (not called by hand)
78
+ and the test stays pinned to the option's actual dest.
79
+ """
80
+
81
+ @click.group(cls=GroupWithGlobalOptions)
82
+ def root() -> None:
83
+ pass
84
+
85
+ @root.group("sub", cls=GroupWithGlobalOptions)
86
+ def sub() -> None:
87
+ pass
88
+
89
+ @sub.command("act")
90
+ def act() -> None:
91
+ h = runtime.default_headers
92
+ if h is None:
93
+ click.echo("HEADERS=NONE")
94
+ else:
95
+ click.echo("HEADERS=" + "|".join(f"{k}={v}" for k, v in sorted(h.items())))
96
+
97
+ return root
98
+
99
+
71
100
  class TestGlobalOptionsAtAnyLevel:
72
101
  def test_debug_on_leaf_command(self) -> None:
73
102
  result = make_runner().invoke(_build_cli(), ["tasks", "act", "--debug"])
@@ -225,10 +254,12 @@ class TestGlobalOptionNamesInventory:
225
254
 
226
255
  # Reverse direction: every global option name must be an actual
227
256
  # ``_Runtime`` field. ``_apply_global_to_runtime`` applies values with a
228
- # bare ``setattr(runtime, name, value)``; ``_Runtime`` is neither frozen
229
- # nor slotted, so a name without a matching field would silently create
230
- # a stray attribute (a no-op option) rather than erroring. Pinning the
231
- # 1:1 here is what lets that ``setattr`` skip name validation.
257
+ # bare ``setattr(runtime, name, value)`` (``default_headers`` is the one
258
+ # exception: it merges into the existing dict, but still writes that same
259
+ # field); ``_Runtime`` is neither frozen nor slotted, so a name without a
260
+ # matching field would silently create a stray attribute (a no-op option)
261
+ # rather than erroring. Pinning the 1:1 here is what lets that ``setattr``
262
+ # skip name validation.
232
263
  runtime_fields = frozenset(f.name for f in dataclasses.fields(_Runtime))
233
264
  for name in GLOBAL_OPTION_NAMES:
234
265
  assert name in runtime_fields, (
@@ -327,6 +358,58 @@ class TestGlobalOptionsSingleSource:
327
358
  )
328
359
 
329
360
 
361
+ class TestDefaultHeadersMergeAcrossLevels:
362
+ """``--set-default-header`` is the only repeatable (accumulative) global, so
363
+ unlike the scalar globals — which are last-wins, see
364
+ ``TestGlobalOptionsAtAnyLevel.test_leaf_overrides_root`` — headers given at
365
+ different points in the command path must *merge* per-header rather than the
366
+ inner level's dict replacing the outer one wholesale.
367
+
368
+ Driven through a real ``GroupWithGlobalOptions`` tree, so Click parsing,
369
+ ``_consume_global_options`` and ``_apply_global_to_runtime`` all run end to
370
+ end and the assertions track the option's real dest."""
371
+
372
+ def test_headers_from_different_levels_merge(self) -> None:
373
+ result = make_runner().invoke(
374
+ _build_cli_echoing_default_headers(),
375
+ [
376
+ "--set-default-header",
377
+ "X-Root=root",
378
+ "--set-default-header",
379
+ "X-Shared=root",
380
+ "sub",
381
+ "act",
382
+ "--set-default-header",
383
+ "X-Leaf=leaf",
384
+ "--set-default-header",
385
+ "X-Shared=leaf",
386
+ ],
387
+ )
388
+ assert result.exit_code == 0, full_output(result)
389
+ # X-Root (outer) survives; the X-Shared collision resolves to the deeper
390
+ # (leaf) value; keys are echoed sorted.
391
+ assert "HEADERS=X-Leaf=leaf|X-Root=root|X-Shared=leaf" in full_output(result)
392
+
393
+ def test_outer_header_not_clobbered_when_inner_omits_flag(self) -> None:
394
+ """A header set at an outer level must persist when an inner level is
395
+ reached without its own ``--set-default-header`` (the inner level's
396
+ DEFAULT-sourced empty value is skipped, so nothing overwrites it)."""
397
+ result = make_runner().invoke(
398
+ _build_cli_echoing_default_headers(),
399
+ ["--set-default-header", "X-Root=root", "sub", "act"],
400
+ )
401
+ assert result.exit_code == 0, full_output(result)
402
+ assert "HEADERS=X-Root=root" in full_output(result)
403
+
404
+ def test_no_header_flag_leaves_runtime_unset(self) -> None:
405
+ """With the flag absent everywhere, the merge branch is never entered:
406
+ ``runtime.default_headers`` stays at its ``None`` unset sentinel rather
407
+ than becoming a stray empty dict."""
408
+ result = make_runner().invoke(_build_cli_echoing_default_headers(), ["sub", "act"])
409
+ assert result.exit_code == 0, full_output(result)
410
+ assert "HEADERS=NONE" in full_output(result)
411
+
412
+
330
413
  def _bash_complete(cmd: click.Command, args: list[str]) -> list[str]:
331
414
  """Drive click's bash completion machinery and return the suggested values."""
332
415
  from click.shell_completion import ShellComplete
@@ -35,7 +35,7 @@ from asana_api_cli.cli import main
35
35
  FIXTURES = Path(__file__).parent / "fixtures" / "generate_python"
36
36
  _UPDATE = os.environ.get("UPDATE_GENERATE_SNAPSHOTS") == "1"
37
37
 
38
- _SNAPSHOT_ASANA_VERSION = "5.2.4"
38
+ _SNAPSHOT_ASANA_VERSION = "5.2.5"
39
39
  pytestmark = pytest.mark.skipif(
40
40
  version("asana") != _SNAPSHOT_ASANA_VERSION,
41
41
  reason=(
@@ -19,13 +19,27 @@ bump fails loudly and forces a conscious classification — a global flag
19
19
  (Configuration) or a common per-command ``(kwargs: ...)`` option, with the
20
20
  matching SDK-destination label (see ``docs/cli-sdk-mapping.md``).
21
21
 
22
- A third guard pins which methods perform a multipart file upload. The CLI
23
- exposes the ``--multibyte-filenames`` extension flag only on upload commands,
24
- detected at runtime by a cheap proxy (an op declaring a ``file`` opt — see
25
- ``_Operation.does_upload``). ``test_upload_detection_matches_multipart_population``
26
- proves that proxy equals the true signal (a source scan for assignment into
27
- ``local_var_files``), so a future SDK that adds or renames an upload endpoint
28
- fails here rather than silently dropping the flag from a command that needs it.
22
+ Two further guards prove the CLI's runtime *classifiers* still agree with the
23
+ SDK source on the installed version each derives both sides from the SDK, so
24
+ neither needs maintenance across a bump:
25
+
26
+ * ``test_upload_detection_matches_multipart_population`` the
27
+ ``--multibyte-filenames`` flag is exposed only on upload commands, detected by
28
+ a cheap proxy (an op declaring a ``file`` opt see ``_Operation.does_upload``).
29
+ The test proves that proxy equals the true signal (a source scan for assignment
30
+ into ``local_var_files``).
31
+ * ``test_iterator_detection_matches_source_construction`` — array-response
32
+ endpoints return a lazy ``PageIterator`` / ``EventIterator`` the runtime
33
+ materializes with ``list(...)``; the runtime detects them by a cheap proxy
34
+ (``:return:`` type ending in ``Array`` — see ``_Operation.returns_iterator``).
35
+ The test proves that proxy equals the true signal (a source scan for the
36
+ iterator constructor).
37
+
38
+ The *which-commands* question — the exact set of upload / iterator endpoints —
39
+ is snapshotted per-command in ``tests/fixtures/cli_surface.json`` (the
40
+ ``does_upload`` / ``returns_iterator`` fields), so an SDK bump that adds or
41
+ removes such an endpoint surfaces in that fixture's diff during the normal regen
42
+ step, with no hand-maintained set to update here.
29
43
  """
30
44
 
31
45
  from __future__ import annotations
@@ -40,7 +54,7 @@ import asana
40
54
 
41
55
  from asana_api_cli.cli import _enumerate_api_classes, _operations_for
42
56
 
43
- # Identical across all ``*_with_http_info`` methods in python-asana 5.2.4.
57
+ # Identical across all ``*_with_http_info`` methods in python-asana 5.2.5.
44
58
  EXPECTED_ALL_PARAMS: frozenset[str] = frozenset(
45
59
  {
46
60
  "async_req",
@@ -98,102 +112,6 @@ EXPECTED_CONFIGURATION_ATTRS: frozenset[str] = frozenset(
98
112
  }
99
113
  )
100
114
 
101
- # The one python-asana method that performs a multipart file upload — i.e. it
102
- # assigns into ``local_var_files`` (every other method has only the empty
103
- # ``local_var_files = {}`` boilerplate). The CLI exposes ``--multibyte-filenames``
104
- # only on upload commands, detected at runtime by a cheap proxy (an op declaring
105
- # a ``file`` opt; see ``_Operation.does_upload``). This anchor plus the
106
- # proxy-equality check below pin that proxy to the true multipart signal.
107
- EXPECTED_MULTIPART_UPLOAD_METHODS: frozenset[tuple[str, str]] = frozenset(
108
- {("AttachmentsApi", "create_attachment_for_object")}
109
- )
110
-
111
- # The python-asana methods whose response is an array — they return a lazy
112
- # ``PageIterator`` / ``EventIterator`` (built in the ``*_with_http_info`` source)
113
- # under the default flags, which the CLI materializes with ``list(...)``. The
114
- # runtime detects them via a cheap proxy (the ``:return:`` type ends in
115
- # ``Array``; see ``_Operation.returns_iterator``). This anchor plus the
116
- # proxy-equality check below pin that proxy to the true iterator-construction
117
- # signal so an SDK bump that adds, removes, or reshapes an array endpoint fails
118
- # loudly. (73 methods in python-asana 5.2.4.)
119
- EXPECTED_ITERATOR_RETURNING_METHODS: frozenset[tuple[str, str]] = frozenset(
120
- {
121
- ("AccessRequestsApi", "get_access_requests"),
122
- ("AllocationsApi", "get_allocations"),
123
- ("AttachmentsApi", "get_attachments_for_object"),
124
- ("AuditLogAPIApi", "get_audit_log_events"),
125
- ("BatchAPIApi", "create_batch_request"),
126
- ("BudgetsApi", "get_budgets"),
127
- ("CustomFieldSettingsApi", "get_custom_field_settings_for_goal"),
128
- ("CustomFieldSettingsApi", "get_custom_field_settings_for_portfolio"),
129
- ("CustomFieldSettingsApi", "get_custom_field_settings_for_project"),
130
- ("CustomFieldSettingsApi", "get_custom_field_settings_for_team"),
131
- ("CustomFieldsApi", "get_custom_fields_for_workspace"),
132
- ("CustomTypesApi", "get_custom_types"),
133
- ("EventsApi", "get_events"),
134
- ("GoalRelationshipsApi", "get_goal_relationships"),
135
- ("GoalsApi", "get_goals"),
136
- ("GoalsApi", "get_parent_goals_for_goal"),
137
- ("MembershipsApi", "get_memberships"),
138
- ("PortfolioMembershipsApi", "get_portfolio_memberships"),
139
- ("PortfolioMembershipsApi", "get_portfolio_memberships_for_portfolio"),
140
- ("PortfoliosApi", "get_items_for_portfolio"),
141
- ("PortfoliosApi", "get_portfolios"),
142
- ("ProjectMembershipsApi", "get_project_memberships_for_project"),
143
- ("ProjectPortfolioSettingsApi", "get_project_portfolio_settings_for_portfolio"),
144
- ("ProjectPortfolioSettingsApi", "get_project_portfolio_settings_for_project"),
145
- ("ProjectStatusesApi", "get_project_statuses_for_project"),
146
- ("ProjectTemplatesApi", "get_project_templates"),
147
- ("ProjectTemplatesApi", "get_project_templates_for_team"),
148
- ("ProjectsApi", "get_projects"),
149
- ("ProjectsApi", "get_projects_for_task"),
150
- ("ProjectsApi", "get_projects_for_team"),
151
- ("ProjectsApi", "get_projects_for_workspace"),
152
- ("ProjectsApi", "search_projects_for_workspace"),
153
- ("RatesApi", "get_rates"),
154
- ("ReactionsApi", "get_reactions_on_object"),
155
- ("RolesApi", "get_roles"),
156
- ("SectionsApi", "get_sections_for_project"),
157
- ("StatusUpdatesApi", "get_statuses_for_object"),
158
- ("StoriesApi", "get_stories_for_goal"),
159
- ("StoriesApi", "get_stories_for_task"),
160
- ("TagsApi", "get_tags"),
161
- ("TagsApi", "get_tags_for_task"),
162
- ("TagsApi", "get_tags_for_workspace"),
163
- ("TaskTemplatesApi", "get_task_templates"),
164
- ("TasksApi", "get_dependencies_for_task"),
165
- ("TasksApi", "get_dependents_for_task"),
166
- ("TasksApi", "get_subtasks_for_task"),
167
- ("TasksApi", "get_tasks"),
168
- ("TasksApi", "get_tasks_for_project"),
169
- ("TasksApi", "get_tasks_for_section"),
170
- ("TasksApi", "get_tasks_for_tag"),
171
- ("TasksApi", "get_tasks_for_user_task_list"),
172
- ("TasksApi", "search_tasks_for_workspace"),
173
- ("TeamMembershipsApi", "get_team_memberships"),
174
- ("TeamMembershipsApi", "get_team_memberships_for_team"),
175
- ("TeamMembershipsApi", "get_team_memberships_for_user"),
176
- ("TeamsApi", "get_teams_for_user"),
177
- ("TeamsApi", "get_teams_for_workspace"),
178
- ("TimePeriodsApi", "get_time_periods"),
179
- ("TimeTrackingCategoriesApi", "get_time_tracking_categories"),
180
- ("TimeTrackingCategoriesApi", "get_time_tracking_entries_for_time_tracking_category"),
181
- ("TimeTrackingEntriesApi", "get_time_tracking_entries"),
182
- ("TimeTrackingEntriesApi", "get_time_tracking_entries_for_task"),
183
- ("TimesheetApprovalStatusesApi", "get_timesheet_approval_statuses"),
184
- ("TypeaheadApi", "typeahead_for_workspace"),
185
- ("UsersApi", "get_favorites_for_user"),
186
- ("UsersApi", "get_users"),
187
- ("UsersApi", "get_users_for_team"),
188
- ("UsersApi", "get_users_for_workspace"),
189
- ("WebhooksApi", "get_webhooks"),
190
- ("WorkspaceMembershipsApi", "get_workspace_memberships_for_user"),
191
- ("WorkspaceMembershipsApi", "get_workspace_memberships_for_workspace"),
192
- ("WorkspacesApi", "get_workspace_events"),
193
- ("WorkspacesApi", "get_workspaces"),
194
- }
195
- )
196
-
197
115
 
198
116
  def _all_params_for(func: Any) -> frozenset[str]:
199
117
  """Collect the ``all_params.append('x')`` string literals from a method body.
@@ -341,14 +259,6 @@ def test_no_unknown_configuration_settable_attrs() -> None:
341
259
  def test_upload_detection_matches_multipart_population() -> None:
342
260
  multipart = _methods_populating_local_var_files()
343
261
  assert multipart, "no method populates local_var_files — SDK introspection broke"
344
- assert multipart == EXPECTED_MULTIPART_UPLOAD_METHODS, (
345
- "the set of multipart-upload methods drifted:\n"
346
- f" added: {sorted(multipart - EXPECTED_MULTIPART_UPLOAD_METHODS)}\n"
347
- f" removed: {sorted(EXPECTED_MULTIPART_UPLOAD_METHODS - multipart)}\n"
348
- "A new/renamed upload endpoint must get the per-command "
349
- "--multibyte-filenames flag (cli.py:_make_command, gated by "
350
- "_Operation.does_upload). See docs/sdk-deviations.md."
351
- )
352
262
  via_proxy = _upload_ops_via_does_upload()
353
263
  assert via_proxy == multipart, (
354
264
  "_Operation.does_upload (the 'has a file opt' proxy) no longer matches "
@@ -365,14 +275,6 @@ def test_iterator_detection_matches_source_construction() -> None:
365
275
  assert constructed, (
366
276
  "no method constructs a PageIterator/EventIterator — SDK introspection broke"
367
277
  )
368
- assert constructed == EXPECTED_ITERATOR_RETURNING_METHODS, (
369
- "the set of iterator-returning methods drifted:\n"
370
- f" added: {sorted(constructed - EXPECTED_ITERATOR_RETURNING_METHODS)}\n"
371
- f" removed: {sorted(EXPECTED_ITERATOR_RETURNING_METHODS - constructed)}\n"
372
- "These are exactly the calls execute_call_plan materializes with "
373
- "list(...); review the change and regenerate the set. See "
374
- "_Operation.returns_iterator."
375
- )
376
278
  via_proxy = _iterator_ops_via_returns_iterator()
377
279
  assert via_proxy == constructed, (
378
280
  "_Operation.returns_iterator (the ':return: *Array' proxy) no longer "
File without changes
File without changes