invoke-toolkit 0.0.4__tar.gz → 0.0.7__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.
- invoke_toolkit-0.0.7/.gitignore +9 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/PKG-INFO +7 -2
- invoke_toolkit-0.0.7/docs/.gitignore +1 -0
- invoke_toolkit-0.0.7/docs/_quarto.yml +84 -0
- invoke_toolkit-0.0.7/docs/index.qmd +30 -0
- invoke_toolkit-0.0.7/output.log +87 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/pyproject.toml +41 -19
- invoke_toolkit-0.0.7/src/invoke_toolkit/__init__.py +7 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/collections.py +203 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/config/config.py +1 -22
- invoke_toolkit-0.0.7/src/invoke_toolkit/context/context.py +121 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/context/types.py +207 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/executor.py +119 -6
- invoke_toolkit-0.0.7/src/invoke_toolkit/extensions/tasks/__init__.py +3 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/extensions/tasks/collections.py +20 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/extensions/tasks/config.py +79 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/loader/entrypoint.py +184 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/output/console.py +26 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/program/main.py +1 -1
- invoke_toolkit-0.0.7/src/invoke_toolkit/program/program.py +199 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/tasks/__init__.py +1 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/tasks/tasks.py +143 -0
- invoke_toolkit-0.0.7/src/invoke_toolkit/utils/text.py +14 -0
- invoke_toolkit-0.0.7/tasks.py +305 -0
- invoke_toolkit-0.0.7/tests/conftest.py +42 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/program/main.py +2 -2
- invoke_toolkit-0.0.7/tests/program/tasks/coll1.py +5 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/script/test_script.py +3 -2
- invoke_toolkit-0.0.7/tests/tasks/test_extensions_config.py +38 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/test_collection.py +3 -3
- invoke_toolkit-0.0.7/tests/test_context_class.py +39 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/test_executor.py +4 -4
- invoke_toolkit-0.0.7/tests/test_loader.py +70 -0
- invoke_toolkit-0.0.7/tests/test_parsing.py +36 -0
- invoke_toolkit-0.0.7/uv.lock +1328 -0
- invoke_toolkit-0.0.4/.gitignore +0 -4
- invoke_toolkit-0.0.4/common-2.23.0.tgz +0 -0
- invoke_toolkit-0.0.4/local_tasks.py +0 -6
- invoke_toolkit-0.0.4/src/invoke_toolkit/__init__.py +0 -40
- invoke_toolkit-0.0.4/src/invoke_toolkit/collections.py +0 -112
- invoke_toolkit-0.0.4/src/invoke_toolkit/context/context.py +0 -34
- invoke_toolkit-0.0.4/src/invoke_toolkit/context/types.py +0 -29
- invoke_toolkit-0.0.4/src/invoke_toolkit/output/console.py +0 -15
- invoke_toolkit-0.0.4/src/invoke_toolkit/program/program.py +0 -104
- invoke_toolkit-0.0.4/src/invoke_toolkit/tasks/plugin.py +0 -18
- invoke_toolkit-0.0.4/tasks.py +0 -138
- invoke_toolkit-0.0.4/tests/conftest.py +0 -15
- invoke_toolkit-0.0.4/tests/program/tasks/coll1.py +0 -5
- invoke_toolkit-0.0.4/uv.lock +0 -717
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/.envrc +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/LICENSE.txt +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/README.md +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/dist/.gitignore +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/__main__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/config/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/config/status_helper.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/context/__init__.py +0 -0
- {invoke_toolkit-0.0.4/src/invoke_toolkit/log → invoke_toolkit-0.0.7/src/invoke_toolkit/extensions}/__init__.py +0 -0
- {invoke_toolkit-0.0.4/src/invoke_toolkit → invoke_toolkit-0.0.7/src/invoke_toolkit/extensions}/tasks/dist.py +0 -0
- {invoke_toolkit-0.0.4/src/invoke_toolkit/runners → invoke_toolkit-0.0.7/src/invoke_toolkit/log}/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/log/logger.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/output/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/output/utils.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/program/__init__.py +0 -0
- {invoke_toolkit-0.0.4/src/invoke_toolkit/scripts → invoke_toolkit-0.0.7/src/invoke_toolkit/runners}/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/runners/rich.py +0 -0
- {invoke_toolkit-0.0.4/src/invoke_toolkit/tasks → invoke_toolkit-0.0.7/src/invoke_toolkit/scripts}/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/scripts/loader.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/utils/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/utils/inspection.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/__init__.py +0 -0
- {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/program/tasks/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: invoke-toolkit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: A set of extended APIs for PyInvoke for composable scripts, plugins and richer output
|
|
5
5
|
Project-URL: Documentation, https://github.com/D3f0/invoke-toolkit#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/D3f0/invoke-toolkit/issues
|
|
@@ -22,7 +22,12 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Classifier: Topic :: System :: Software Distribution
|
|
24
24
|
Classifier: Topic :: System :: Systems Administration
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: appdirs
|
|
27
|
+
Requires-Dist: invoke>=2.2.1
|
|
28
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
29
|
+
Requires-Dist: rich>=14.2.0
|
|
30
|
+
Requires-Dist: tomlkit>=0.13.3
|
|
26
31
|
Requires-Dist: typing-extensions>=4.13.2
|
|
27
32
|
Description-Content-Type: text/markdown
|
|
28
33
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/.quarto/
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
project:
|
|
2
|
+
type: website
|
|
3
|
+
|
|
4
|
+
website:
|
|
5
|
+
title: "Invoke Toolkit"
|
|
6
|
+
navbar:
|
|
7
|
+
background: primary
|
|
8
|
+
search: true
|
|
9
|
+
left:
|
|
10
|
+
- text: "Home"
|
|
11
|
+
file: index.qmd
|
|
12
|
+
- text: "Reference"
|
|
13
|
+
file: reference/index.qmd
|
|
14
|
+
tools:
|
|
15
|
+
- icon: github
|
|
16
|
+
menu:
|
|
17
|
+
- text: Source Code
|
|
18
|
+
href: https://github.com/D3f0/invoke-toolkit
|
|
19
|
+
- text: Report a Bug
|
|
20
|
+
href: https://github.com/D3f0/invoke-toolkit/issues/new
|
|
21
|
+
# sidebar:
|
|
22
|
+
# # style: ""
|
|
23
|
+
# search: true
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# tell quarto to read the generated sidebar
|
|
27
|
+
metadata-files:
|
|
28
|
+
- reference/_sidebar.yml
|
|
29
|
+
|
|
30
|
+
# tell quarto to read the generated styles
|
|
31
|
+
format:
|
|
32
|
+
html:
|
|
33
|
+
css:
|
|
34
|
+
- reference/_styles-quartodoc.css
|
|
35
|
+
|
|
36
|
+
quartodoc:
|
|
37
|
+
# the name used to import the package you want to create reference docs for
|
|
38
|
+
package: invoke_toolkit
|
|
39
|
+
|
|
40
|
+
# write sidebar and style data
|
|
41
|
+
sidebar: reference/_sidebar.yml
|
|
42
|
+
css: reference/_styles-quartodoc.css
|
|
43
|
+
|
|
44
|
+
sections:
|
|
45
|
+
- title: Invoke Toolkit special classes
|
|
46
|
+
desc: Functions to inspect .
|
|
47
|
+
contents:
|
|
48
|
+
# the functions being documented in the package.
|
|
49
|
+
# you can refer to anything: class methods, modules, etc..
|
|
50
|
+
- program.program
|
|
51
|
+
- context.InvokeToolkitContext
|
|
52
|
+
- tasks.plugin
|
|
53
|
+
- tasks.dist
|
|
54
|
+
- context.types
|
|
55
|
+
- config.config
|
|
56
|
+
- config.status_helper
|
|
57
|
+
- output.console
|
|
58
|
+
- output.utils
|
|
59
|
+
- utils.inspection
|
|
60
|
+
- collections
|
|
61
|
+
# - scripts.loader
|
|
62
|
+
# - program.main
|
|
63
|
+
# - log.logger
|
|
64
|
+
# - runners.rich
|
|
65
|
+
# - executor
|
|
66
|
+
|
|
67
|
+
- title: Builtin collections
|
|
68
|
+
desc: >
|
|
69
|
+
A
|
|
70
|
+
B
|
|
71
|
+
C
|
|
72
|
+
package: invoke_toolkit.tasks
|
|
73
|
+
contents:
|
|
74
|
+
- plugin
|
|
75
|
+
- dist
|
|
76
|
+
|
|
77
|
+
- title: More
|
|
78
|
+
desc: Invoke reference
|
|
79
|
+
package: invoke
|
|
80
|
+
contents:
|
|
81
|
+
# the functions being documented in the package.
|
|
82
|
+
# you can refer to anything: class methods, modules, etc..
|
|
83
|
+
- tasks.task
|
|
84
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Welcome to Invoke Toolkit documentation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
<img alt="PyPI - Package Version" src="https://img.shields.io/pypi/v/invoke-toolkit">
|
|
5
|
+
|
|
6
|
+
Invoke Toolkit is a set of opinionated extensions to the popular [Python invoke library](https://pyinvoke.org).
|
|
7
|
+
|
|
8
|
+
- Add's some `Context` attributes
|
|
9
|
+
- Renames the `inv`/`invoke` to `it`/`invoke-toolkit`, reads the same `tasks.py`.
|
|
10
|
+
- Replaces `print()` with `rich`'s Console print (internal logging uses `rich` logger): `it -d`.
|
|
11
|
+
- Command echo defaults to `stderr` (`it -e`)
|
|
12
|
+
-
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The recommended way to use `invoke-toolkit` is through `uv` package manager:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install invoke-toolkit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Simple task example
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from invoke_toolkit import task, Context
|
|
26
|
+
|
|
27
|
+
@task()
|
|
28
|
+
def build(ctx: Context):
|
|
29
|
+
ctx.run("quarto build")
|
|
30
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[16:44:34] DEBUG No default namespace provided, trying to load one from disk program.py:467
|
|
2
|
+
DEBUG FilesystemLoader find starting at '/Users/nahueldefosse/workspace/OSS/invoke-toolkit' loader.py:124
|
|
3
|
+
DEBUG Found module: ModuleSpec(name='tasks', loader=<_frozen_importlib_external.SourceFileLoader object at 0x10752ba10>, loader.py:148
|
|
4
|
+
origin='/Users/nahueldefosse/workspace/OSS/invoke-toolkit/tasks.py')
|
|
5
|
+
DEBUG Didn't see any /Users/nahueldefosse/workspace/OSS/invoke-toolkit/invoke.yaml, skipping. config.py:903
|
|
6
|
+
DEBUG Didn't see any /Users/nahueldefosse/workspace/OSS/invoke-toolkit/invoke.yml, skipping. config.py:903
|
|
7
|
+
DEBUG Didn't see any /Users/nahueldefosse/workspace/OSS/invoke-toolkit/invoke.json, skipping. config.py:903
|
|
8
|
+
DEBUG Merging config sources in order onto new empty _config... config.py:947
|
|
9
|
+
DEBUG Defaults: {'run': {'asynchronous': False, 'disown': False, 'dry': False, 'echo': False, 'echo_stdin': None, 'encoding': None, config.py:949
|
|
10
|
+
'env': {}, 'err_stream': None, 'fallback': True, 'hide': None, 'in_stream': None, 'out_stream': None, 'echo_format':
|
|
11
|
+
'[bold]{command}[/bold]', 'pty': False, 'replace_env': False, 'shell': '/bin/bash', 'warn': False, 'watchers': []},
|
|
12
|
+
'runners': {'local': <class 'invoke_toolkit.runners.rich.NoStdoutRunner'>}, 'sudo': {'password': None, 'prompt': '[sudo]
|
|
13
|
+
password: ', 'user': None}, 'tasks': {'auto_dash_names': True, 'collection_name': 'tasks', 'dedupe': True, 'executor_class':
|
|
14
|
+
None, 'ignore_unknown_help': False, 'search_root': None}, 'timeouts': {'command': None}}
|
|
15
|
+
DEBUG Collection-driven: {} config.py:951
|
|
16
|
+
DEBUG System-wide config file (/etc/invoke.py): {} config.py:977
|
|
17
|
+
DEBUG Per-user config file (/Users/nahueldefosse/.invoke.py): {'docker': {'command': 'podman'}} config.py:977
|
|
18
|
+
DEBUG Per-project config file (/Users/nahueldefosse/workspace/OSS/invoke-toolkit/invoke.py): {} config.py:977
|
|
19
|
+
DEBUG Environment variable config: {} config.py:956
|
|
20
|
+
DEBUG Runtime config file has not been loaded yet, skipping config.py:974
|
|
21
|
+
DEBUG Overrides: {} config.py:959
|
|
22
|
+
DEBUG Modifications: {} config.py:961
|
|
23
|
+
DEBUG Deletions: {} config.py:963
|
|
24
|
+
DEBUG Trying to load internal invoke-toolkit collections program.py:167
|
|
25
|
+
INFO Importing submodules in invoke_toolkit.extensions.tasks collections.py:34
|
|
26
|
+
ERROR Error loading config: No module named 'yaml' collections.py:49
|
|
27
|
+
DEBUG 🔧 Adding config to module 📦 collections: dict_keys(['a']) collections.py:163
|
|
28
|
+
DEBUG Adding root collection configuration: None collections.py:174
|
|
29
|
+
DEBUG Adding <parser/Context 'version'> parser.py:78
|
|
30
|
+
DEBUG Adding <parser/Context 'build': {'target': <Argument: target_ (t) [list]>, 'output': <Argument: output (o)>}> parser.py:78
|
|
31
|
+
DEBUG Adding <parser/Context 'clean'> parser.py:78
|
|
32
|
+
DEBUG Adding <parser/Context 'test' (t): {'debug': <Argument: debug (d) [bool]>, 'verbose': <Argument: verbose (v) [bool]>, parser.py:78
|
|
33
|
+
'capture-output': <Argument: capture_output (c) [bool]>, 'picked': <Argument: picked (p) [bool]>}>
|
|
34
|
+
DEBUG Adding <parser/Context 'release': {'skip-sync': <Argument: skip_sync (s) [bool]>}> parser.py:78
|
|
35
|
+
DEBUG Adding <parser/Context 'docs-api-build' (b): {'config': <Argument: config (c)>, 'filter': <Argument: filter_ (f)>, 'dry-run': parser.py:78
|
|
36
|
+
<Argument: dry_run (d) [bool]>, 'watch': <Argument: watch (w) [bool]>, 'verbose': <Argument: verbose (v) [bool]>}>
|
|
37
|
+
DEBUG Adding <parser/Context 'docs-preview' (p)> parser.py:78
|
|
38
|
+
DEBUG Adding <parser/Context 'run-in-container': {'image': <Argument: image (i)>, 'container-tool': <Argument: container_tool (c)>, parser.py:78
|
|
39
|
+
'command': <Argument: command (o)>, 'rm': <Argument: rm (r) [bool]>, 'interactive': <Argument: interactive (n) [bool]>, 'tty':
|
|
40
|
+
<Argument: tty (t) [bool]>}>
|
|
41
|
+
DEBUG Adding <parser/Context 'venv-search' (v)> parser.py:78
|
|
42
|
+
DEBUG Adding <parser/Context 'env': {'clear': <Argument: clear (c) [bool]>}> parser.py:78
|
|
43
|
+
DEBUG Adding <parser/Context 'collections.list' (collections)> parser.py:78
|
|
44
|
+
DEBUG Adding <parser/Context 'collections.add': {'plugin-spec': <Argument: plugin_spec (p) *>}> parser.py:78
|
|
45
|
+
DEBUG Adding <parser/Context 'collections.remove': {'name': <Argument: name (n) *>}> parser.py:78
|
|
46
|
+
DEBUG Parsing tasks against <Collection 'tasks': build, clean, docs-api-build, docs-preview, env, release, run-in-container, test, program.py:765
|
|
47
|
+
venv-search, version, collections..., dist...>
|
|
48
|
+
DEBUG Initialized with context: <parser/Context: {'command-timeout': <Argument: command-timeout (T) [int]>, 'complete': <Argument: parser.py:238
|
|
49
|
+
complete [bool]>, 'config': <Argument: config (f)>, 'debug': <Argument: debug (d) [bool]>, 'dry': <Argument: dry (R) [bool]>,
|
|
50
|
+
'echo': <Argument: echo (e) [bool]>, 'help': <Argument: help (h) ?>, 'hide': <Argument: hide>, 'list': <Argument: list (l)
|
|
51
|
+
?>, 'list-depth': <Argument: list-depth (D) [int]>, 'list-format': <Argument: list-format (F)>, 'print-completion-script':
|
|
52
|
+
<Argument: print-completion-script>, 'prompt-for-sudo-password': <Argument: prompt-for-sudo-password [bool]>, 'pty':
|
|
53
|
+
<Argument: pty (p) [bool]>, 'version': <Argument: version (V) [bool]>, 'warn-only': <Argument: warn-only (w) [bool]>,
|
|
54
|
+
'write-pyc': <Argument: write-pyc [bool]>, 'internal-col': <Argument: internal-col (x) [bool]>, 'collection': <Argument:
|
|
55
|
+
collection (c)>, 'no-dedupe': <Argument: no-dedupe [bool]>, 'search-root': <Argument: search-root (r)>}>
|
|
56
|
+
DEBUG Available contexts: {'version': <parser/Context 'version'>, 'build': <parser/Context 'build': {'target': <Argument: target_ parser.py:243
|
|
57
|
+
(t) [list]>, 'output': <Argument: output (o)>}>, 'clean': <parser/Context 'clean'>, 'test': <parser/Context 'test' (t):
|
|
58
|
+
{'debug': <Argument: debug (d) [bool]>, 'verbose': <Argument: verbose (v) [bool]>, 'capture-output': <Argument:
|
|
59
|
+
capture_output (c) [bool]>, 'picked': <Argument: picked (p) [bool]>}>, 'release': <parser/Context 'release': {'skip-sync':
|
|
60
|
+
<Argument: skip_sync (s) [bool]>}>, 'docs-api-build': <parser/Context 'docs-api-build' (b): {'config': <Argument: config
|
|
61
|
+
(c)>, 'filter': <Argument: filter_ (f)>, 'dry-run': <Argument: dry_run (d) [bool]>, 'watch': <Argument: watch (w) [bool]>,
|
|
62
|
+
'verbose': <Argument: verbose (v) [bool]>}>, 'docs-preview': <parser/Context 'docs-preview' (p)>, 'run-in-container':
|
|
63
|
+
<parser/Context 'run-in-container': {'image': <Argument: image (i)>, 'container-tool': <Argument: container_tool (c)>,
|
|
64
|
+
'command': <Argument: command (o)>, 'rm': <Argument: rm (r) [bool]>, 'interactive': <Argument: interactive (n) [bool]>,
|
|
65
|
+
'tty': <Argument: tty (t) [bool]>}>, 'venv-search': <parser/Context 'venv-search' (v)>, 'env': <parser/Context 'env':
|
|
66
|
+
{'clear': <Argument: clear (c) [bool]>}>, 'collections.list': <parser/Context 'collections.list' (collections)>,
|
|
67
|
+
'collections.add': <parser/Context 'collections.add': {'plugin-spec': <Argument: plugin_spec (p) *>}>, 'collections.remove':
|
|
68
|
+
<parser/Context 'collections.remove': {'name': <Argument: name (n) *>}>}
|
|
69
|
+
DEBUG Wrapping up context None parser.py:336
|
|
70
|
+
DEBUG Starting argv: [] parser.py:123
|
|
71
|
+
DEBUG ParseMachine: 'context' => 'end' parser.py:227
|
|
72
|
+
DEBUG Wrapping up context None parser.py:336
|
|
73
|
+
DEBUG Resulting task contexts: [] program.py:772
|
|
74
|
+
build Builds distributable package
|
|
75
|
+
clean Cleans dist
|
|
76
|
+
docs-api-build (b) Runs uv run quartodoc build with the provided arguments.
|
|
77
|
+
docs-preview (p) Runs quarto preview to visualize the documentation.
|
|
78
|
+
env (re)creates the virtual environment (with uv)
|
|
79
|
+
release Tags (if the git repo is clean) proposing the next tag
|
|
80
|
+
run-in-container Runs invoke-toolkit in a container.
|
|
81
|
+
test (t) Runs pytest and exposes some commonly used flags
|
|
82
|
+
venv-search (v)
|
|
83
|
+
version Shows package version (git based)
|
|
84
|
+
collections.add Adds a collection from a local path or a remote git repository
|
|
85
|
+
collections.list (collections) List all the collections available to invoke-toolkit
|
|
86
|
+
collections.remove Removes a collection previously added with collections.add
|
|
87
|
+
DEBUG Received a possibly-skippable exception: Exit() program.py:400
|
|
@@ -3,7 +3,7 @@ name = "invoke-toolkit"
|
|
|
3
3
|
dynamic = ["version"]
|
|
4
4
|
description = "A set of extended APIs for PyInvoke for composable scripts, plugins and richer output"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
keywords = []
|
|
9
9
|
authors = [
|
|
@@ -26,9 +26,15 @@ classifiers = [
|
|
|
26
26
|
"Topic :: System :: Systems Administration",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
+
"invoke>=2.2.1",
|
|
30
|
+
"rich>=14.2.0",
|
|
29
31
|
"typing-extensions>=4.13.2",
|
|
32
|
+
"appdirs",
|
|
33
|
+
"tomlkit>=0.13.3",
|
|
34
|
+
"pyyaml>=6.0.3",
|
|
30
35
|
]
|
|
31
36
|
|
|
37
|
+
# Git based versioning, uses git tags
|
|
32
38
|
[build-system]
|
|
33
39
|
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
34
40
|
build-backend = "hatchling.build"
|
|
@@ -37,16 +43,20 @@ build-backend = "hatchling.build"
|
|
|
37
43
|
source = "uv-dynamic-versioning"
|
|
38
44
|
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
|
|
47
|
+
[tool.commitizen]
|
|
48
|
+
name = "cz_conventional_commits"
|
|
49
|
+
tag_format = "v$version"
|
|
50
|
+
version_scheme = "pep440"
|
|
51
|
+
version_provider = "uv"
|
|
52
|
+
update_changelog_on_bump = true
|
|
53
|
+
major_version_zero = true
|
|
45
54
|
|
|
46
55
|
[project.scripts]
|
|
47
|
-
|
|
56
|
+
it = "invoke_toolkit.program.main:program.run"
|
|
48
57
|
invoke-toolkit = "invoke_toolkit.program.main:program.run"
|
|
49
58
|
|
|
59
|
+
|
|
50
60
|
[project.urls]
|
|
51
61
|
Documentation = "https://github.com/D3f0/invoke-toolkit#readme"
|
|
52
62
|
Issues = "https://github.com/D3f0/invoke-toolkit/issues"
|
|
@@ -77,7 +87,6 @@ exclude_lines = [
|
|
|
77
87
|
"if TYPE_CHECKING:",
|
|
78
88
|
]
|
|
79
89
|
|
|
80
|
-
|
|
81
90
|
[tool.hatch.envs.dev]
|
|
82
91
|
dependencies = [
|
|
83
92
|
# Invoke's original set of tasks
|
|
@@ -92,14 +101,13 @@ run = "run-coverage --no-cov"
|
|
|
92
101
|
test = "pytest -v {}"
|
|
93
102
|
testdbg = "pytest -v --pdb {}"
|
|
94
103
|
|
|
95
|
-
[tool.hatch.envs.docs]
|
|
96
|
-
dependencies = [
|
|
97
|
-
"sphinx"
|
|
98
|
-
]
|
|
99
|
-
|
|
100
104
|
|
|
101
105
|
[tool.ruff.lint.per-file-ignores]
|
|
102
106
|
"*tasks*" = ["ARG001"]
|
|
107
|
+
"__init__.py" = ["E402"]
|
|
108
|
+
"**/{tests,docs}/*" = ["E402"]
|
|
109
|
+
"src/invoke_toolkit/program/program.py" = ["E402"]
|
|
110
|
+
|
|
103
111
|
|
|
104
112
|
[dependency-groups]
|
|
105
113
|
dev = [
|
|
@@ -108,6 +116,12 @@ dev = [
|
|
|
108
116
|
"pylint>=3.2.7",
|
|
109
117
|
"pylint-per-file-ignores>=1.0.0",
|
|
110
118
|
"pytest>=8.3.5",
|
|
119
|
+
"pytest-cov>=5.0.0",
|
|
120
|
+
"pytest-picked>=0.5.1",
|
|
121
|
+
"pytest-sugar>=1.1.1",
|
|
122
|
+
]
|
|
123
|
+
doc = [
|
|
124
|
+
"quartodoc>=0.11.1",
|
|
111
125
|
]
|
|
112
126
|
|
|
113
127
|
[tool.pylint.main]
|
|
@@ -142,8 +156,8 @@ disable = [
|
|
|
142
156
|
|
|
143
157
|
|
|
144
158
|
per-file-ignores = [
|
|
145
|
-
"tasks.py:missing-function-docstring,missing-class-docstring,missing-module-docstring",
|
|
146
|
-
"src/invoke_toolkit/program/program.py:ungrouped-imports",
|
|
159
|
+
"tasks.py:missing-function-docstring,missing-class-docstring,missing-module-docstring,invalid-name",
|
|
160
|
+
"src/invoke_toolkit/program/program.py:ungrouped-imports,redefined-builtin,raise-missing-from,attribute-defined-outside-init",
|
|
147
161
|
"src/**/__init__.py:missing-module-docstring",
|
|
148
162
|
"src/invoke_toolkit/scripts/loader.py:redefined-builtin",
|
|
149
163
|
"src/**/tasks/*.py:unused-argument",
|
|
@@ -151,9 +165,17 @@ per-file-ignores = [
|
|
|
151
165
|
"src/invoke_toolkit/utils/inspection.py:logging-fstring-interpolation",
|
|
152
166
|
"**/types.py:too-many-arguments,too-few-public-methods,too-many-locals,missing-class-docstring",
|
|
153
167
|
"**/context.py:too-few-public-methods",
|
|
154
|
-
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
"tests/**/*.py:missing-function-docstring,unused-argument,missing-module-docstring,missing-class-docstring",
|
|
168
|
+
# This is for the list initialization of var-args in Invoke
|
|
169
|
+
"**/*.py:dangerous-default-value,line-too-long,logging-format-interpolation,missing-module-docstring,missing-class-docstring",
|
|
170
|
+
"tests/**/*.py:missing-function-docstring,unused-argument,missing-module-docstring,missing-class-docstring,redefined-outer-name",
|
|
158
171
|
"tests/conftest.py:missing-module-docstring,redefined-outer-name",
|
|
172
|
+
"src/invoke_toolkit/loader/entrypoint.py:missing-function-docstring,logging-fstring-interpolation,import-outside-toplevel,broad-exception-caught,unused-argument,arguments-differ",
|
|
159
173
|
]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
[tool.pytest.ini_options]
|
|
177
|
+
testpaths = ["tests"]
|
|
178
|
+
addopts = "--cov=invoke_toolkit"
|
|
179
|
+
filterwarnings = []
|
|
180
|
+
|
|
181
|
+
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Extended collection with package inspection"""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import pkgutil
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
from typing import Any, Callable, Dict, Optional, Union, overload
|
|
9
|
+
|
|
10
|
+
from invoke.collection import Collection
|
|
11
|
+
from invoke.tasks import Task
|
|
12
|
+
from invoke.util import debug
|
|
13
|
+
from invoke_toolkit.tasks import InvokeToolkitTask
|
|
14
|
+
from invoke_toolkit.utils.inspection import get_calling_file_path
|
|
15
|
+
from logging import getLogger
|
|
16
|
+
|
|
17
|
+
logger = getLogger("invoke")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CollectionError(Exception):
|
|
21
|
+
"""Base class for import discovery errors"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CollectionNotImportedError(CollectionError): ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CollectionCantFindModulePathError(CollectionError): ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def import_submodules(package_name: str) -> Dict[str, ModuleType]:
|
|
31
|
+
"""
|
|
32
|
+
Import all submodules of a module from an imported module
|
|
33
|
+
"""
|
|
34
|
+
logger.info("Importing submodules in %s", package_name)
|
|
35
|
+
try:
|
|
36
|
+
package = importlib.import_module(package_name)
|
|
37
|
+
except ImportError as import_error:
|
|
38
|
+
msg = f"Module {package_name} not imported"
|
|
39
|
+
raise CollectionNotImportedError(msg) from import_error
|
|
40
|
+
result = {}
|
|
41
|
+
path = getattr(package, "__path__", None)
|
|
42
|
+
if path is None:
|
|
43
|
+
raise CollectionCantFindModulePathError(package)
|
|
44
|
+
for _loader, name, _is_pkg in pkgutil.walk_packages(package.__path__):
|
|
45
|
+
try:
|
|
46
|
+
result[name] = importlib.import_module(package_name + "." + name)
|
|
47
|
+
except (ImportError, SyntaxError) as error:
|
|
48
|
+
# TODO: Add a flag to show loading exceptions
|
|
49
|
+
logger.exception(error)
|
|
50
|
+
if not name.startswith("__"):
|
|
51
|
+
logger.error(f"Error loading {name} from {package_name}: {error}")
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def clean_collection(collection: "InvokeToolkitCollection") -> None:
|
|
57
|
+
"""Removes tasks that are imported from other modules or start with underscores"""
|
|
58
|
+
|
|
59
|
+
def user_facing_task(name: str, task: Task) -> bool:
|
|
60
|
+
if name.startswith("_"):
|
|
61
|
+
debug(f"Not adding task {name=} because it starts with _")
|
|
62
|
+
return False
|
|
63
|
+
*_, module_name = task.__module__.split(".")
|
|
64
|
+
# Make sure composed_col is composed-col
|
|
65
|
+
if module_name.replace("_", "-") != collection.name:
|
|
66
|
+
debug(f"Imported task from another place {module_name=} {collection.name=}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
collection.tasks = {
|
|
72
|
+
name: task
|
|
73
|
+
for name, task in collection.tasks.items()
|
|
74
|
+
# if not name.startswith("_")
|
|
75
|
+
if user_facing_task(name, task)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class InvokeToolkitCollection(Collection):
|
|
80
|
+
"""
|
|
81
|
+
This Collection allows to load sub-collections from python package paths/namespaces
|
|
82
|
+
like `myscripts.tasks.*`
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
@overload
|
|
86
|
+
def __init__(self, **kwargs) -> None: ...
|
|
87
|
+
|
|
88
|
+
@overload
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
name: str,
|
|
92
|
+
*args: Union[Task, InvokeToolkitTask, Collection, "InvokeToolkitCollection"],
|
|
93
|
+
**kwargs,
|
|
94
|
+
) -> None: ...
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self, *args: Union[str, Task, InvokeToolkitTask, Collection], **kwargs
|
|
98
|
+
) -> None:
|
|
99
|
+
debug(f"Instantiating collection with {args=} and {kwargs=}")
|
|
100
|
+
super().__init__(*args, **kwargs)
|
|
101
|
+
|
|
102
|
+
def _add_object(self, obj: Any, name: Optional[str] = None) -> None:
|
|
103
|
+
method: Callable
|
|
104
|
+
if isinstance(obj, (Task, InvokeToolkitTask)):
|
|
105
|
+
method = self.add_task
|
|
106
|
+
elif isinstance(obj, (InvokeToolkitCollection, Collection, ModuleType)):
|
|
107
|
+
method = self.add_collection
|
|
108
|
+
else:
|
|
109
|
+
raise TypeError("No idea how to insert {!r}!".format(type(obj)))
|
|
110
|
+
method(obj, name=name)
|
|
111
|
+
|
|
112
|
+
def add_collections_from_namespace(self, namespace: str):
|
|
113
|
+
"""Iterates over a namespace and imports the submodules"""
|
|
114
|
+
# Attempt simple import
|
|
115
|
+
ok = False
|
|
116
|
+
if namespace not in sys.modules:
|
|
117
|
+
debug(f"Attempting simple import of {namespace}")
|
|
118
|
+
try:
|
|
119
|
+
importlib.import_module(namespace)
|
|
120
|
+
ok = True
|
|
121
|
+
except ImportError:
|
|
122
|
+
logger.warning(f"Failed to import {namespace}")
|
|
123
|
+
|
|
124
|
+
if not ok:
|
|
125
|
+
debug("Starting stack inspection to find module")
|
|
126
|
+
# Trying to import relative to caller's script
|
|
127
|
+
caller_path = get_calling_file_path(
|
|
128
|
+
# We're going to get the path of the file where this call
|
|
129
|
+
# was made
|
|
130
|
+
find_call_text=".add_collections_from_namespace("
|
|
131
|
+
)
|
|
132
|
+
debug(f"Adding {caller_path} in order to import {namespace}")
|
|
133
|
+
sys.path.append(caller_path)
|
|
134
|
+
# This should work even if there's no __init__ alongside the
|
|
135
|
+
# program main
|
|
136
|
+
importlib.import_module(namespace)
|
|
137
|
+
|
|
138
|
+
for name, module in import_submodules(namespace).items():
|
|
139
|
+
coll = InvokeToolkitCollection.from_module(module)
|
|
140
|
+
# TODO: Discover if the namespace has configuration
|
|
141
|
+
# collection.configure(config)
|
|
142
|
+
self.add_collection(coll=coll, name=name)
|
|
143
|
+
|
|
144
|
+
def load_plugins(self):
|
|
145
|
+
"""
|
|
146
|
+
This will call to .add_collections_from_namespace but will ensure to
|
|
147
|
+
add the plugin folder to the sys.path
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def load_directory(self, directory: Union[str, Path]) -> None:
|
|
151
|
+
"""Loads tasks from a folder"""
|
|
152
|
+
if isinstance(directory, str):
|
|
153
|
+
path = Path(directory)
|
|
154
|
+
elif not isinstance(directory, Path):
|
|
155
|
+
msg = f"The directory to load plugins is not a str/Path: {directory}:{type(directory)}"
|
|
156
|
+
raise TypeError(msg)
|
|
157
|
+
else:
|
|
158
|
+
path = directory
|
|
159
|
+
|
|
160
|
+
existing_paths = {pth for pth in sys.path if Path(pth).is_dir()}
|
|
161
|
+
if path not in existing_paths:
|
|
162
|
+
sys.path.append(str(path))
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_module(
|
|
166
|
+
cls, module, name=None, config=None, loaded_from=None, auto_dash_names=None
|
|
167
|
+
) -> "InvokeToolkitCollection":
|
|
168
|
+
return super().from_module(module, name, config, loaded_from, auto_dash_names)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_package(
|
|
172
|
+
cls, package_path: str, into: "InvokeToolkitCollection"
|
|
173
|
+
) -> "InvokeToolkitCollection": # pylint: disable=too-many-branches)
|
|
174
|
+
"""
|
|
175
|
+
Creates a collection from a package and configures it
|
|
176
|
+
"""
|
|
177
|
+
ns = into or cls()
|
|
178
|
+
global_config: dict[str, str | dict[str, str]] = {}
|
|
179
|
+
for name, module in import_submodules(package_path).items():
|
|
180
|
+
config = getattr(module, "config", None)
|
|
181
|
+
collection: "InvokeToolkitCollection" = ns.from_module(module)
|
|
182
|
+
clean_collection(collection=collection)
|
|
183
|
+
# TODO: Namespaced configuration seems to be an not present when merged!§
|
|
184
|
+
# if config and isinstance(config, (dict, )):
|
|
185
|
+
# debug(f"🔧 Adding config to module 📦 {name}: {config}")
|
|
186
|
+
# collection.configure(config)
|
|
187
|
+
if config:
|
|
188
|
+
# FIXME: Detect coitions
|
|
189
|
+
if isinstance(config, (dict,)):
|
|
190
|
+
debug(f"🔧 Adding config to module 📦 {name}: {config.keys()}")
|
|
191
|
+
prefixed_config = {name: config}
|
|
192
|
+
global_config.update(**prefixed_config)
|
|
193
|
+
else:
|
|
194
|
+
debug(f"In the module {module} the config name is for {config}")
|
|
195
|
+
ns.add_collection(
|
|
196
|
+
collection,
|
|
197
|
+
name=name,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if global_config:
|
|
201
|
+
debug(f"Adding root collection configuration: {config}")
|
|
202
|
+
ns.configure(global_config)
|
|
203
|
+
return ns
|
|
@@ -3,16 +3,12 @@ Custom config class passed in every context class as .config
|
|
|
3
3
|
This module defines some functions/callables
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Any, Dict
|
|
6
|
+
from typing import Any, Dict
|
|
7
7
|
|
|
8
8
|
from invoke.config import Config
|
|
9
|
-
from rich import inspect
|
|
10
|
-
from rich.status import Status
|
|
11
9
|
|
|
12
|
-
from invoke_toolkit.output import get_console, rich_exit
|
|
13
10
|
|
|
14
11
|
from ..runners.rich import NoStdoutRunner
|
|
15
|
-
from .status_helper import StatusHelper
|
|
16
12
|
|
|
17
13
|
|
|
18
14
|
class InvokeToolkitConfig(Config):
|
|
@@ -21,8 +17,6 @@ class InvokeToolkitConfig(Config):
|
|
|
21
17
|
such as .cd, .run, etc.
|
|
22
18
|
"""
|
|
23
19
|
|
|
24
|
-
_current_status: Optional[Status]
|
|
25
|
-
|
|
26
20
|
@staticmethod
|
|
27
21
|
def global_defaults() -> Dict[str, Any]:
|
|
28
22
|
"""
|
|
@@ -38,21 +32,6 @@ class InvokeToolkitConfig(Config):
|
|
|
38
32
|
.. versionadded:: 1.0
|
|
39
33
|
"""
|
|
40
34
|
ret: Dict[str, Any] = Config.global_defaults()
|
|
41
|
-
console = get_console()
|
|
42
|
-
|
|
43
|
-
status_helper = StatusHelper(console=console)
|
|
44
|
-
|
|
45
|
-
ret.update(
|
|
46
|
-
{
|
|
47
|
-
"console": console,
|
|
48
|
-
"status": status_helper.status,
|
|
49
|
-
"status_stop": status_helper.status_stop,
|
|
50
|
-
"status_update": status_helper.status_update,
|
|
51
|
-
"rich_exit": rich_exit,
|
|
52
|
-
"print": console.print,
|
|
53
|
-
"inspect": inspect,
|
|
54
|
-
}
|
|
55
|
-
)
|
|
56
35
|
ret["runners"]["local"] = NoStdoutRunner
|
|
57
36
|
ret["run"]["echo_format"] = "[bold]{command}[/bold]"
|
|
58
37
|
return ret
|