invoke-toolkit 0.0.65__tar.gz → 0.0.66__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 (149) hide show
  1. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/PKG-INFO +69 -1
  2. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/README.md +68 -0
  3. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/pyproject.toml +15 -2
  4. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/__init__.py +3 -0
  5. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/collections.py +15 -6
  6. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/completion.py +20 -29
  7. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/config/config.py +14 -14
  8. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/config/registry.py +4 -3
  9. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/config/schema.py +3 -2
  10. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/config/status_helper.py +19 -18
  11. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/context/async_tools.py +3 -2
  12. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/context/context.py +13 -23
  13. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/context/types.py +18 -16
  14. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/executor.py +75 -57
  15. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/extensions/tasks/config.py +4 -6
  16. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/extensions/tasks/create.py +106 -55
  17. invoke_toolkit-0.0.66/src/invoke_toolkit/field_resolvers.py +333 -0
  18. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/output/console.py +19 -19
  19. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/parser.py +12 -6
  20. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/program/program.py +17 -18
  21. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/runners/rich.py +2 -2
  22. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/scripts/loader.py +2 -3
  23. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/tasks/__init__.py +1 -0
  24. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/tasks/autocomplete.py +4 -3
  25. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/tasks/cache.py +40 -73
  26. invoke_toolkit-0.0.66/src/invoke_toolkit/tasks/fields.py +121 -0
  27. invoke_toolkit-0.0.66/src/invoke_toolkit/tasks/fields.pyi +79 -0
  28. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/tasks/tasks.py +345 -113
  29. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/testing.py +1 -2
  30. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/utils/fzf.py +7 -7
  31. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/utils/inspection.py +1 -2
  32. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/utils/singleton.py +2 -1
  33. invoke_toolkit-0.0.66/templates/provider-template/.gitignore.jinja +6 -0
  34. invoke_toolkit-0.0.66/templates/provider-template/README.md.jinja +45 -0
  35. invoke_toolkit-0.0.66/templates/provider-template/copier.yml +52 -0
  36. invoke_toolkit-0.0.66/templates/provider-template/pyproject.toml.jinja +39 -0
  37. invoke_toolkit-0.0.66/templates/provider-template/src/{{package_slug}}/__init__.py.jinja +23 -0
  38. invoke_toolkit-0.0.66/templates/provider-template/tests/test_resolver.py.jinja +11 -0
  39. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/conftest.py +5 -8
  40. invoke_toolkit-0.0.66/tests/examples/local_field_resolver/tasks.py +47 -0
  41. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/extensions/test_create.py +81 -2
  42. invoke_toolkit-0.0.66/tests/fixtures/future_fields.py +11 -0
  43. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_annotated_help.py +1 -1
  44. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_async_support.py +32 -3
  45. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_completion_with_choices.py +0 -9
  46. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_config_registry.py +2 -4
  47. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_enum_arguments.py +1 -1
  48. invoke_toolkit-0.0.66/tests/test_field_typing.py +28 -0
  49. invoke_toolkit-0.0.66/tests/test_fields.py +895 -0
  50. invoke_toolkit-0.0.66/tests/typecheck/fields.py +51 -0
  51. invoke_toolkit-0.0.66/tests/typecheck/fields_negative.py.fixture +18 -0
  52. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/.gitignore +0 -0
  53. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/LICENSE.txt +0 -0
  54. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/__main__.py +0 -0
  55. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/config/__init__.py +0 -0
  56. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/context/__init__.py +0 -0
  57. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/extensions/__init__.py +0 -0
  58. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/extensions/tasks/__init__.py +0 -0
  59. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/extensions/tasks/dist.py +0 -0
  60. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/extensions/tasks/shell.py +0 -0
  61. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/loader/entrypoint.py +0 -0
  62. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/log/__init__.py +0 -0
  63. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/log/logger.py +0 -0
  64. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/output/__init__.py +0 -0
  65. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/output/utils.py +0 -0
  66. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/program/__init__.py +0 -0
  67. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/program/main.py +0 -0
  68. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/py.typed +0 -0
  69. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/runners/__init__.py +0 -0
  70. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/scripts/__init__.py +0 -0
  71. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/tasks/types.py +0 -0
  72. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/utils/__init__.py +0 -0
  73. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/src/invoke_toolkit/utils/text.py +0 -0
  74. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/templates/package-template/.gitignore.jinja +0 -0
  75. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/templates/package-template/README.md.jinja +0 -0
  76. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/templates/package-template/copier.yml +0 -0
  77. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/templates/package-template/pyproject.toml.jinja +0 -0
  78. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/templates/package-template/src/{{package_slug}}/__init__.py.jinja +0 -0
  79. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/templates/package-template/src/{{package_slug}}/tasks.py.jinja +0 -0
  80. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/__init__.py +0 -0
  81. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/cached_completion/tasks.py +0 -0
  82. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/completion_with_config/.gitignore +0 -0
  83. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/completion_with_config/tasks.py +0 -0
  84. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/config_schema/tasks.py +0 -0
  85. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/enum_select_size/tasks.py +0 -0
  86. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/fzf_selector/tasks.py +0 -0
  87. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/literal_set_level/tasks.py +0 -0
  88. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/local_tasks/local_tasks.py +0 -0
  89. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/examples/local_tasks/tasks.py +0 -0
  90. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/extensions/conftest.py +0 -0
  91. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/extensions/test_config_tasks.py +0 -0
  92. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/extensions/test_package_template_entrypoint.py +0 -0
  93. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/extensions/test_shell_tasks.py +0 -0
  94. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/__init__.py +0 -0
  95. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/all-four/invoke.json +0 -0
  96. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/all-four/invoke.py +0 -0
  97. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/all-four/invoke.yml +0 -0
  98. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/collection.py +0 -0
  99. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/echo.yaml +0 -0
  100. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/json/invoke.json +0 -0
  101. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/json-and-python/invoke.json +0 -0
  102. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/json-and-python/invoke.py +0 -0
  103. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/no-dedupe.yaml +0 -0
  104. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/no-echo.yaml +0 -0
  105. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/package/invoke.yml +0 -0
  106. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/package/tasks/__init__.py +0 -0
  107. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/python/invoke.py +0 -0
  108. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/runtime.py +0 -0
  109. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/three-of-em/invoke.json +0 -0
  110. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/three-of-em/invoke.py +0 -0
  111. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/three-of-em/invoke.yml +0 -0
  112. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/underscores/tasks.py +0 -0
  113. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/yaml/explicit.py +0 -0
  114. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/yaml/tasks.py +0 -0
  115. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/yml/explicit.py +0 -0
  116. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/yml/invoke.yml +0 -0
  117. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/configs/yml/tasks.py +0 -0
  118. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/_support/has_modules.py +0 -0
  119. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/conftest.py +0 -0
  120. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/original_invoke/test_config.py +0 -0
  121. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/program/main.py +0 -0
  122. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/program/tasks/__init__.py +0 -0
  123. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/program/tasks/coll1.py +0 -0
  124. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/script/test_script.py +0 -0
  125. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/tasks/test_cache.py +0 -0
  126. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/tasks/test_extensions_config.py +0 -0
  127. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_alias_self_reference.py +0 -0
  128. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_async_edge_cases.py +0 -0
  129. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_cofig_class.py +0 -0
  130. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_collection.py +0 -0
  131. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_collection_configure.py +0 -0
  132. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_config_helper.py +0 -0
  133. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_config_schema.py +0 -0
  134. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_console.py +0 -0
  135. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_context_class.py +0 -0
  136. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_disable_status_cli.py +0 -0
  137. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_executor.py +0 -0
  138. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_file_completion.py +0 -0
  139. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_global_context.py +0 -0
  140. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_help_flags.py +0 -0
  141. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_invoke_3_compat.py +0 -0
  142. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_invoke_compatibility.py +0 -0
  143. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_list_filter_flags.py +0 -0
  144. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_loader.py +0 -0
  145. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_parsing.py +0 -0
  146. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_proctitle.py +0 -0
  147. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_program_with_collection.py +0 -0
  148. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/test_toplevel.py +0 -0
  149. {invoke_toolkit-0.0.65 → invoke_toolkit-0.0.66}/tests/utils/test_fzf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: invoke-toolkit
3
- Version: 0.0.65
3
+ Version: 0.0.66
4
4
  Summary: Extended APIs for PyInvoke: composable task plugins, typed CLI args, Rich output, shell completion, and PEP 723 script support
5
5
  Project-URL: Documentation, https://github.com/D3f0/invoke-toolkit#readme
6
6
  Project-URL: Issues, https://github.com/D3f0/invoke-toolkit/issues
@@ -167,6 +167,74 @@ To enable caching, install with the cache extra:
167
167
  pip install invoke-toolkit[cache]
168
168
  ```
169
169
 
170
+ ## Dynamic task defaults
171
+
172
+ Use `Field` when an argument default must be computed from the final task
173
+ context or resolved from a URI. Explicit command-line values always take
174
+ precedence.
175
+
176
+ Bind a resolver once, then reuse the resulting callable for scalar and file
177
+ defaults. A local resolver always returns one string value per request. The
178
+ resolver receives every URI using that callback and scheme in one batch.
179
+
180
+ ```python
181
+ from invoke_toolkit import Context, Field, FilePath, task
182
+
183
+
184
+ def resolve_bw(ctx: Context, requests: list) -> dict[str, str]:
185
+ return {
186
+ request.parameter: ctx.run("bw get password ...", hide=True).stdout.strip()
187
+ for request in requests
188
+ }
189
+
190
+
191
+ BitwardenField = Field(resolver=resolve_bw)
192
+ ExistingFile = FilePath(exists=True, dir_okay=False)
193
+
194
+
195
+ @task
196
+ def deploy(
197
+ ctx: Context,
198
+ password: str = BitwardenField(default="bw://PASSWORD_ID"),
199
+ config: ExistingFile = BitwardenField(
200
+ default="bw://CONFIG_ID", cleanup="task"
201
+ ),
202
+ ) -> None:
203
+ ...
204
+ ```
205
+
206
+ For `str` fields, the resolver string reaches the task unchanged. For `Path` or
207
+ `FilePath` fields, `Field.create_temporary_file()` writes that string to a
208
+ managed temporary file and passes its `Path` to the task. Override that method
209
+ in a `Field` subclass to control file creation; resolvers do not return paths.
210
+
211
+ For omitted arguments, lookup precedence is explicit CLI/Python/call value,
212
+ then `INVOKE_<PARAMETER>` environment value, then the resolved `ctx.config`
213
+ value, then the declared `Field` default or factory. URI values from config use
214
+ the same local or entry-point resolver dispatch as declared URI defaults.
215
+
216
+ `Field(default_factory=callback)` runs only if no higher-precedence value is
217
+ available. Factories and resolvers never run during help, listing, or shell
218
+ completion; `Path`/`FilePath` completion continues to work normally for
219
+ explicit values.
220
+
221
+ Temporary `Path` files from resolver-bound `Field` instances are owned by
222
+ invoke-toolkit. `cleanup="pipeline"` is the default and keeps the file through
223
+ expanded pre/main/post execution; `cleanup="task"` removes it when that task
224
+ returns. Cleanup runs after failures and cancellation.
225
+
226
+ Installed `invoke_toolkit.field_resolver` entry points remain supported for
227
+ compatibility but issue a warning recommending a task-local resolver. Generate a
228
+ resolver-only provider package with:
229
+
230
+ ```console
231
+ intk -x create.package --provider op
232
+ ```
233
+
234
+ Provider packages expose no task collection. Providers return text only; for
235
+ `Path` fields invoke-toolkit materializes and cleans the resulting temporary
236
+ file according to that Field's cleanup lifetime.
237
+
170
238
  ## Development
171
239
 
172
240
  This project utilizes the `pre-commit` framework, make sure you run:
@@ -106,6 +106,74 @@ To enable caching, install with the cache extra:
106
106
  pip install invoke-toolkit[cache]
107
107
  ```
108
108
 
109
+ ## Dynamic task defaults
110
+
111
+ Use `Field` when an argument default must be computed from the final task
112
+ context or resolved from a URI. Explicit command-line values always take
113
+ precedence.
114
+
115
+ Bind a resolver once, then reuse the resulting callable for scalar and file
116
+ defaults. A local resolver always returns one string value per request. The
117
+ resolver receives every URI using that callback and scheme in one batch.
118
+
119
+ ```python
120
+ from invoke_toolkit import Context, Field, FilePath, task
121
+
122
+
123
+ def resolve_bw(ctx: Context, requests: list) -> dict[str, str]:
124
+ return {
125
+ request.parameter: ctx.run("bw get password ...", hide=True).stdout.strip()
126
+ for request in requests
127
+ }
128
+
129
+
130
+ BitwardenField = Field(resolver=resolve_bw)
131
+ ExistingFile = FilePath(exists=True, dir_okay=False)
132
+
133
+
134
+ @task
135
+ def deploy(
136
+ ctx: Context,
137
+ password: str = BitwardenField(default="bw://PASSWORD_ID"),
138
+ config: ExistingFile = BitwardenField(
139
+ default="bw://CONFIG_ID", cleanup="task"
140
+ ),
141
+ ) -> None:
142
+ ...
143
+ ```
144
+
145
+ For `str` fields, the resolver string reaches the task unchanged. For `Path` or
146
+ `FilePath` fields, `Field.create_temporary_file()` writes that string to a
147
+ managed temporary file and passes its `Path` to the task. Override that method
148
+ in a `Field` subclass to control file creation; resolvers do not return paths.
149
+
150
+ For omitted arguments, lookup precedence is explicit CLI/Python/call value,
151
+ then `INVOKE_<PARAMETER>` environment value, then the resolved `ctx.config`
152
+ value, then the declared `Field` default or factory. URI values from config use
153
+ the same local or entry-point resolver dispatch as declared URI defaults.
154
+
155
+ `Field(default_factory=callback)` runs only if no higher-precedence value is
156
+ available. Factories and resolvers never run during help, listing, or shell
157
+ completion; `Path`/`FilePath` completion continues to work normally for
158
+ explicit values.
159
+
160
+ Temporary `Path` files from resolver-bound `Field` instances are owned by
161
+ invoke-toolkit. `cleanup="pipeline"` is the default and keeps the file through
162
+ expanded pre/main/post execution; `cleanup="task"` removes it when that task
163
+ returns. Cleanup runs after failures and cancellation.
164
+
165
+ Installed `invoke_toolkit.field_resolver` entry points remain supported for
166
+ compatibility but issue a warning recommending a task-local resolver. Generate a
167
+ resolver-only provider package with:
168
+
169
+ ```console
170
+ intk -x create.package --provider op
171
+ ```
172
+
173
+ Provider packages expose no task collection. Providers return text only; for
174
+ `Path` fields invoke-toolkit materializes and cleans the resulting temporary
175
+ file according to that Field's cleanup lifetime.
176
+
109
177
  ## Development
110
178
 
111
179
  This project utilizes the `pre-commit` framework, make sure you run:
@@ -126,6 +126,14 @@ exclude_lines = [
126
126
  ]
127
127
 
128
128
 
129
+ [tool.ruff]
130
+ target-version = "py310"
131
+ extend-exclude = ["tests/original_invoke"]
132
+
133
+ [tool.ruff.lint]
134
+ extend-select = ["UP"]
135
+
136
+
129
137
  [tool.ruff.lint.per-file-ignores]
130
138
  "*tasks*" = ["ARG001"]
131
139
  "__init__.py" = ["E402"]
@@ -158,7 +166,6 @@ ci = [
158
166
  "nox>=2025.10.16",
159
167
  ]
160
168
  dev = [
161
-
162
169
  "copier>=9.16.0",
163
170
  "diskcache>=5.6.0",
164
171
  "hunter>=3.7.0",
@@ -167,6 +174,7 @@ dev = [
167
174
  "pre-commit-uv>=4.2.0",
168
175
  "pylint>=3.2.7",
169
176
  "pylint-per-file-ignores>=1.0.0",
177
+ "pyright>=1.1.404",
170
178
  "pytest>=9.1.1",
171
179
  "pytest-cov>=5.0.0",
172
180
  "pytest-html>=4.1.1",
@@ -212,7 +220,7 @@ disable = [
212
220
 
213
221
  per-file-ignores = [
214
222
  "tasks.py:missing-function-docstring,missing-class-docstring,missing-module-docstring,invalid-name,too-many-branches",
215
- "src/invoke_toolkit/tasks/tasks.py:too-many-arguments,reimported,import-outside-toplevel,too-many-locals",
223
+ "src/invoke_toolkit/tasks/tasks.py:too-many-arguments,reimported,import-outside-toplevel,too-many-locals,too-many-lines,unidiomatic-typecheck,protected-access",
216
224
  "src/invoke_toolkit/tasks/types.py:invalid-name,import-outside-toplevel,unidiomatic-typecheck",
217
225
  "src/invoke_toolkit/completion.py:too-many-locals,too-many-branches,too-many-statements",
218
226
  "src/invoke_toolkit/program/program.py:ungrouped-imports,redefined-builtin,raise-missing-from,attribute-defined-outside-init,redefined-builtin,possibly-used-before-assignment,no-else-raise",
@@ -232,6 +240,11 @@ per-file-ignores = [
232
240
  "src/invoke_toolkit/config/config.py:import-outside-toplevel",
233
241
  "src/invoke_toolkit/config/registry.py:import-outside-toplevel,unidiomatic-typecheck",
234
242
  "src/invoke_toolkit/extensions/tasks/config.py:too-many-locals,import-outside-toplevel",
243
+ "src/invoke_toolkit/extensions/tasks/create.py:too-many-locals,too-many-branches",
244
+ "src/invoke_toolkit/executor.py:too-many-locals,protected-access,duplicate-code",
245
+ "src/invoke_toolkit/field_resolvers.py:too-many-locals,too-many-branches",
246
+ "src/invoke_toolkit/tasks/fields.py:invalid-name,too-few-public-methods",
247
+ "tests/typecheck/fields.py:invalid-name",
235
248
  ]
236
249
 
237
250
 
@@ -16,6 +16,9 @@ from invoke_toolkit.tasks import ( # noqa
16
16
  CacheConfig,
17
17
  cache_stats,
18
18
  cached,
19
+ Field,
20
+ FieldResolutionRequest,
21
+ FieldResolver,
19
22
  call,
20
23
  clear_task_cache,
21
24
  task,
@@ -1,13 +1,15 @@
1
1
  """Extended collection with package inspection"""
2
2
 
3
+ # pylint: disable=ungrouped-imports
3
4
  import hashlib
4
5
  import importlib.util
5
6
  import pkgutil
6
7
  import sys
8
+ from collections.abc import Callable
7
9
  from logging import getLogger
8
10
  from pathlib import Path
9
11
  from types import ModuleType
10
- from typing import TYPE_CHECKING, Any, Callable, overload
12
+ from typing import TYPE_CHECKING, Any, overload
11
13
 
12
14
  try:
13
15
  from typing import override # type: ignore[attr-defined]
@@ -288,17 +290,19 @@ class ToolkitCollection(Collection):
288
290
  )
289
291
 
290
292
  @override
293
+ # pylint: disable=arguments-renamed
291
294
  def configure(
292
295
  self,
293
296
  mapping: dict[str, Any] | None = None,
294
297
  *,
295
298
  schema: "type[ConfigSchema] | ConfigSchema | None" = None,
299
+ **kwargs: Any,
296
300
  ) -> None:
297
301
  """Configure collection with optional attrs schema.
298
-
299
302
  Args:
300
303
  mapping: Traditional dict config (existing behavior)
301
304
  schema: Attrs class (uses defaults) or instance (uses provided values)
305
+ **kwargs: Additional compatibility keyword arguments.
302
306
 
303
307
  Examples:
304
308
  # Traditional dict
@@ -337,9 +341,14 @@ class ToolkitCollection(Collection):
337
341
  if self.name and self._config_schema:
338
342
  register_schema(self.name, self._config_schema)
339
343
 
340
- if mapping:
341
- # Merge mapping over schema defaults (mapping wins)
342
- self._deep_merge(final_mapping, mapping)
344
+ options = kwargs.pop("options", mapping)
345
+ if kwargs:
346
+ unexpected = next(iter(kwargs))
347
+ raise TypeError(f"unexpected keyword argument: {unexpected!r}")
348
+
349
+ if options:
350
+ # Merge options over schema defaults (options wins)
351
+ self._deep_merge(final_mapping, options)
343
352
 
344
353
  if final_mapping:
345
354
  super().configure(final_mapping)
@@ -395,7 +404,7 @@ class ToolkitCollection(Collection):
395
404
  register_schema(name, schema)
396
405
 
397
406
  config = getattr(module, "config", None)
398
- collection: "ToolkitCollection" = ns.from_module(module)
407
+ collection: ToolkitCollection = ns.from_module(module)
399
408
  clean_collection(collection=collection)
400
409
  # TODO: Namespaced configuration seems to be an not present when merged!§
401
410
  # if config and isinstance(config, (dict, )):
@@ -11,7 +11,6 @@ import re
11
11
  import shlex
12
12
  from concurrent.futures import ThreadPoolExecutor
13
13
  from concurrent.futures import TimeoutError as FuturesTimeoutError
14
- from typing import List
15
14
 
16
15
  from invoke.completion.complete import (
17
16
  debug,
@@ -32,7 +31,7 @@ from invoke_toolkit.tasks.types import _FileCompletionMarker
32
31
  def _get_file_completions(
33
32
  marker: "_FileCompletionMarker",
34
33
  incomplete: str,
35
- ) -> List[str]:
34
+ ) -> list[str]:
36
35
  """
37
36
  Get file completions based on marker configuration.
38
37
 
@@ -96,7 +95,7 @@ def _get_file_completions(
96
95
  # Determine prefix to prepend to results
97
96
  dir_prefix = "" if base_dir == "." else base_dir + "/"
98
97
 
99
- files: List[str] = []
98
+ files: list[str] = []
100
99
 
101
100
  try:
102
101
  if marker.pattern:
@@ -148,7 +147,7 @@ def get_choices_for_argument(
148
147
  arg_name: str,
149
148
  incomplete: str = "",
150
149
  config: "ToolkitConfig | None" = None,
151
- ) -> List[str]:
150
+ ) -> list[str]:
152
151
  """
153
152
  Get available choices for an argument in a task.
154
153
 
@@ -244,7 +243,7 @@ def get_choices_for_argument(
244
243
  return []
245
244
 
246
245
 
247
- def _strip_program_name(names: List[str], remainder: str) -> str:
246
+ def _strip_program_name(names: list[str], remainder: str) -> str:
248
247
  """
249
248
  Strip the program name from the invocation string.
250
249
 
@@ -274,7 +273,7 @@ def _strip_program_name(names: List[str], remainder: str) -> str:
274
273
  return invocation
275
274
 
276
275
 
277
- def _get_directory_completions(incomplete: str) -> List[str]:
276
+ def _get_directory_completions(incomplete: str) -> list[str]:
278
277
  """
279
278
  Get directory completions for path arguments like --search-root.
280
279
 
@@ -310,7 +309,7 @@ def _get_directory_completions(incomplete: str) -> List[str]:
310
309
  else:
311
310
  dir_prefix = base_dir + "/"
312
311
 
313
- directories: List[str] = []
312
+ directories: list[str] = []
314
313
 
315
314
  try:
316
315
  if not os.path.isdir(base_dir):
@@ -399,7 +398,7 @@ def _handle_flag_completion(
399
398
 
400
399
 
401
400
  def _try_complete_flag_value(
402
- tokens: List[str],
401
+ tokens: list[str],
403
402
  completing_new_token: bool,
404
403
  initial_context: ParserContext,
405
404
  collection,
@@ -441,7 +440,7 @@ def _try_complete_flag_value(
441
440
  )
442
441
 
443
442
 
444
- def _get_positional_arg_index(context: ParserContext, tokens: List[str]) -> int:
443
+ def _get_positional_arg_index(context: ParserContext, tokens: list[str]) -> int:
445
444
  """
446
445
  Determine which positional argument we're completing based on tokens.
447
446
 
@@ -546,7 +545,7 @@ def _handle_positional_completion(
546
545
 
547
546
 
548
547
  def complete_with_choices(
549
- names: List[str],
548
+ names: list[str],
550
549
  core,
551
550
  initial_context: ParserContext,
552
551
  collection,
@@ -572,7 +571,7 @@ def complete_with_choices(
572
571
  """
573
572
  # Strip out program name
574
573
  invocation = _strip_program_name(names, core.remainder)
575
- debug("Completing for invocation: {!r}".format(invocation))
574
+ debug(f"Completing for invocation: {invocation!r}")
576
575
 
577
576
  # Check if invocation ends with space (user wants to complete next token)
578
577
  completing_new_token = invocation.rstrip() != invocation
@@ -583,32 +582,28 @@ def complete_with_choices(
583
582
  # Handle flags (partial or otherwise)
584
583
  if tokens and tokens[-1].startswith("-"):
585
584
  tail = tokens[-1]
586
- debug("Invocation's tail {!r} is flag-like".format(tail))
585
+ debug(f"Invocation's tail {tail!r} is flag-like")
587
586
 
588
587
  # Parse invocation to obtain current context
589
- contexts: List[ParserContext]
588
+ contexts: list[ParserContext]
590
589
  try:
591
- debug("Seeking context name in tokens: {!r}".format(tokens))
590
+ debug(f"Seeking context name in tokens: {tokens!r}")
592
591
  contexts = parser.parse_argv(tokens)
593
592
  except ParseError as e:
594
- debug(
595
- "Got parser error ({!r}), grabbing last-seen context {!r}".format(
596
- e, e.context
597
- )
598
- )
593
+ debug(f"Got parser error ({e!r}), grabbing last-seen context {e.context!r}")
599
594
  contexts = [e.context] if e.context is not None else []
600
595
 
601
596
  # Fall back to core context if no context seen
602
- debug("Parsed invocation, contexts: {!r}".format(contexts))
597
+ debug(f"Parsed invocation, contexts: {contexts!r}")
603
598
  if not contexts or not contexts[-1]:
604
599
  context = initial_context
605
600
  else:
606
601
  context = contexts[-1]
607
602
 
608
- debug("Selected context: {!r}".format(context))
603
+ debug(f"Selected context: {context!r}")
609
604
 
610
605
  # Check if this flag is known
611
- debug("Looking for {!r} in {!r}".format(tail, context.flags))
606
+ debug(f"Looking for {tail!r} in {context.flags!r}")
612
607
  if tail not in context.flags:
613
608
  debug("Not found, completing with flag names")
614
609
  # Long flags - partial or just the dashes
@@ -633,16 +628,12 @@ def complete_with_choices(
633
628
  raise Exit
634
629
 
635
630
  # Try to parse to get the current context
636
- contexts: List[ParserContext] = []
631
+ contexts: list[ParserContext] = []
637
632
  try:
638
- debug("Parsing tokens for positional completion: {!r}".format(tokens))
633
+ debug(f"Parsing tokens for positional completion: {tokens!r}")
639
634
  contexts = parser.parse_argv(tokens)
640
635
  except ParseError as e:
641
- debug(
642
- "Got parser error ({!r}), grabbing last-seen context {!r}".format(
643
- e, e.context
644
- )
645
- )
636
+ debug(f"Got parser error ({e!r}), grabbing last-seen context {e.context!r}")
646
637
  contexts = [e.context] if e.context is not None else []
647
638
 
648
639
  # Check if we have a task context (not just core context)
@@ -4,7 +4,7 @@ This module defines some functions/callables
4
4
  """
5
5
 
6
6
  import inspect as frame_inspect
7
- from typing import TYPE_CHECKING, Any, Dict, NoReturn, Optional, TypeVar, overload
7
+ from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, overload
8
8
 
9
9
  from invoke.config import Config
10
10
  from invoke.util import debug
@@ -52,10 +52,10 @@ class ToolkitConfig(Config):
52
52
 
53
53
  def __init_subclass__(
54
54
  cls,
55
- prefix: Optional[str] = None,
56
- file_prefix: Optional[str] = None,
57
- env_prefix: Optional[str] = None,
58
- extra_defaults: Optional[Dict[str, Any]] = None,
55
+ prefix: str | None = None,
56
+ file_prefix: str | None = None,
57
+ env_prefix: str | None = None,
58
+ extra_defaults: dict[str, Any] | None = None,
59
59
  **kwargs,
60
60
  ):
61
61
  super().__init_subclass__(**kwargs)
@@ -70,13 +70,13 @@ class ToolkitConfig(Config):
70
70
  else:
71
71
  cls.extra_defaults = None
72
72
 
73
- extra_defaults: Optional[Dict[str, Any]]
73
+ extra_defaults: dict[str, Any] | None
74
74
 
75
75
  # This method is a static method in the super class
76
76
  # but it's converted to class method here, so we can
77
77
  # a reference to the attribute set by __init_subclass__
78
78
  @classmethod
79
- def global_defaults(cls) -> Dict[str, Any]:
79
+ def global_defaults(cls) -> dict[str, Any]:
80
80
  """
81
81
  Return the core default settings for Invoke.
82
82
 
@@ -89,7 +89,7 @@ class ToolkitConfig(Config):
89
89
 
90
90
  .. versionadded:: 1.0
91
91
  """
92
- ret: Dict[str, Any] = Config.global_defaults()
92
+ ret: dict[str, Any] = Config.global_defaults()
93
93
  extra_defaults = getattr(cls, "extra_defaults", None)
94
94
  if extra_defaults:
95
95
  debug(f"Using {cls} extra defaults: {extra_defaults}")
@@ -239,7 +239,7 @@ def _navigate_config_path(config: "ToolkitConfig", path: str) -> tuple[Any, bool
239
239
  return current, True
240
240
 
241
241
 
242
- def _get_task_argument_name(path: str, depth: int = 3) -> Optional[str]: # pylint: disable=too-many-return-statements
242
+ def _get_task_argument_name(path: str, depth: int = 3) -> str | None: # pylint: disable=too-many-return-statements
243
243
  """
244
244
  Try to infer the task argument name from the call stack.
245
245
 
@@ -324,7 +324,7 @@ def get_config_value(
324
324
  path: str,
325
325
  default: Any = _UNDEFINED_DEFAULT,
326
326
  exit_message: str = ...,
327
- exit_code: Optional[int] = None,
327
+ exit_code: int | None = None,
328
328
  required: bool = False,
329
329
  ) -> Any | NoReturn: ...
330
330
 
@@ -347,8 +347,8 @@ def get_config_value(
347
347
  ctx: "ToolkitContext",
348
348
  path: str,
349
349
  default: Any = _UNDEFINED_DEFAULT,
350
- exit_message: Optional[str] = None,
351
- exit_code: Optional[int] = None,
350
+ exit_message: str | None = None,
351
+ exit_code: int | None = None,
352
352
  required: bool = True,
353
353
  ) -> Any | NoReturn: ...
354
354
 
@@ -357,8 +357,8 @@ def get_config_value( # pylint: disable=inconsistent-return-statements
357
357
  ctx: "ToolkitContext",
358
358
  path: str,
359
359
  default: Any = _UNDEFINED_DEFAULT,
360
- exit_message: Optional[str] = None,
361
- exit_code: Optional[int] = None,
360
+ exit_message: str | None = None,
361
+ exit_code: int | None = None,
362
362
  required: bool = False,
363
363
  ) -> Any:
364
364
  """
@@ -2,7 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, Union
5
+ import types
6
+ from typing import TYPE_CHECKING, Any, Union, get_origin
6
7
 
7
8
  import attrs
8
9
 
@@ -187,12 +188,12 @@ def _unwrap_optional_type(type_hint: Any) -> type | None:
187
188
  Returns:
188
189
  The unwrapped type, or the original if not Optional/Union
189
190
  """
190
- origin = getattr(type_hint, "__origin__", None)
191
+ origin = get_origin(type_hint)
191
192
  if origin is None:
192
193
  return type_hint
193
194
 
194
195
  # Handle Union types (including Optional which is Union[X, None])
195
- if origin is Union:
196
+ if origin in (Union, types.UnionType):
196
197
  args = getattr(type_hint, "__args__", ())
197
198
  # Filter out NoneType and return the first non-None type
198
199
  non_none_args = [a for a in args if a is not type(None)]
@@ -3,7 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from typing import Any, Callable, Type, TypeVar, overload
6
+ from typing import Any, TypeVar, overload
7
+ from collections.abc import Callable
7
8
 
8
9
  import attrs
9
10
  import cattrs
@@ -49,7 +50,7 @@ class ConfigSchema:
49
50
  __config_description__: str | None = None
50
51
 
51
52
  @classmethod
52
- def from_dict(cls: Type[T], data: dict[str, Any]) -> T:
53
+ def from_dict(cls: type[T], data: dict[str, Any]) -> T:
53
54
  """Create instance from dict using cattrs.
54
55
 
55
56
  Args:
@@ -2,9 +2,10 @@
2
2
  Class that implements the ctx.status through the config class
3
3
  """
4
4
 
5
+ # pylint: disable=ungrouped-imports
5
6
  from collections import deque
7
+ from collections.abc import Generator
6
8
  from contextlib import contextmanager
7
- from typing import Generator, Optional, Union
8
9
 
9
10
  from rich.console import Console, Group, RenderableType
10
11
  from rich.live import Live
@@ -24,8 +25,8 @@ class NoOpStatus:
24
25
 
25
26
  def __init__(
26
27
  self,
27
- status: Optional[RenderableType] = None,
28
- console: Optional[Console] = None,
28
+ status: RenderableType | None = None,
29
+ console: Console | None = None,
29
30
  ):
30
31
  self.status = status
31
32
  self.console = console or get_console()
@@ -43,11 +44,11 @@ class NoOpStatus:
43
44
 
44
45
  def update(
45
46
  self,
46
- status: Optional[RenderableType] = None,
47
+ status: RenderableType | None = None,
47
48
  *,
48
- spinner: Optional[str] = None,
49
- spinner_style: Optional[StyleType] = None,
50
- speed: Optional[float] = None,
49
+ spinner: str | None = None,
50
+ spinner_style: StyleType | None = None,
51
+ speed: float | None = None,
51
52
  ) -> None:
52
53
  """No-op update"""
53
54
 
@@ -65,7 +66,7 @@ class VerboseStatus: # pylint: disable=too-many-instance-attributes
65
66
  self,
66
67
  status: RenderableType,
67
68
  *,
68
- console: Optional[Console] = None,
69
+ console: Console | None = None,
69
70
  spinner: str = "dots",
70
71
  spinner_style: StyleType = "status.spinner",
71
72
  speed: float = 1.0,
@@ -105,11 +106,11 @@ class VerboseStatus: # pylint: disable=too-many-instance-attributes
105
106
 
106
107
  def update(
107
108
  self,
108
- status: Optional[RenderableType] = None,
109
+ status: RenderableType | None = None,
109
110
  *,
110
- spinner: Optional[str] = None,
111
- spinner_style: Optional[StyleType] = None,
112
- speed: Optional[float] = None,
111
+ spinner: str | None = None,
112
+ spinner_style: StyleType | None = None,
113
+ speed: float | None = None,
113
114
  ) -> None:
114
115
  """Update the status text or spinner parameters."""
115
116
  if status is not None:
@@ -150,7 +151,7 @@ class StatusHelper:
150
151
  it can be accessed from the task's context
151
152
  """
152
153
 
153
- _current_status: Optional[Union[Status, VerboseStatus]]
154
+ _current_status: Status | VerboseStatus | None
154
155
  _disabled: bool
155
156
  _show_command_output: bool
156
157
 
@@ -174,7 +175,7 @@ class StatusHelper:
174
175
  spinner_style: StyleType = "status.spinner",
175
176
  speed: float = 1.0,
176
177
  refresh_per_second: float = 12.5,
177
- ) -> Generator[Union[Status, VerboseStatus], None, None]:
178
+ ) -> Generator[Status | VerboseStatus, None, None]:
178
179
  """Context manager for status management"""
179
180
  if self._disabled:
180
181
  with NoOpStatus(status=status, console=self.console) as noop:
@@ -222,11 +223,11 @@ class StatusHelper:
222
223
 
223
224
  def status_update(
224
225
  self,
225
- status: Optional[RenderableType] = None,
226
+ status: RenderableType | None = None,
226
227
  *,
227
- spinner: Optional[str] = None,
228
- spinner_style: Optional[StyleType] = None,
229
- speed: Optional[float] = None,
228
+ spinner: str | None = None,
229
+ spinner_style: StyleType | None = None,
230
+ speed: float | None = None,
230
231
  ) -> None:
231
232
  """Wrapper on Status.update"""
232
233
  if self._disabled: