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.
Files changed (72) hide show
  1. invoke_toolkit-0.0.7/.gitignore +9 -0
  2. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/PKG-INFO +7 -2
  3. invoke_toolkit-0.0.7/docs/.gitignore +1 -0
  4. invoke_toolkit-0.0.7/docs/_quarto.yml +84 -0
  5. invoke_toolkit-0.0.7/docs/index.qmd +30 -0
  6. invoke_toolkit-0.0.7/output.log +87 -0
  7. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/pyproject.toml +41 -19
  8. invoke_toolkit-0.0.7/src/invoke_toolkit/__init__.py +7 -0
  9. invoke_toolkit-0.0.7/src/invoke_toolkit/collections.py +203 -0
  10. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/config/config.py +1 -22
  11. invoke_toolkit-0.0.7/src/invoke_toolkit/context/context.py +121 -0
  12. invoke_toolkit-0.0.7/src/invoke_toolkit/context/types.py +207 -0
  13. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/executor.py +119 -6
  14. invoke_toolkit-0.0.7/src/invoke_toolkit/extensions/tasks/__init__.py +3 -0
  15. invoke_toolkit-0.0.7/src/invoke_toolkit/extensions/tasks/collections.py +20 -0
  16. invoke_toolkit-0.0.7/src/invoke_toolkit/extensions/tasks/config.py +79 -0
  17. invoke_toolkit-0.0.7/src/invoke_toolkit/loader/entrypoint.py +184 -0
  18. invoke_toolkit-0.0.7/src/invoke_toolkit/output/console.py +26 -0
  19. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/program/main.py +1 -1
  20. invoke_toolkit-0.0.7/src/invoke_toolkit/program/program.py +199 -0
  21. invoke_toolkit-0.0.7/src/invoke_toolkit/tasks/__init__.py +1 -0
  22. invoke_toolkit-0.0.7/src/invoke_toolkit/tasks/tasks.py +143 -0
  23. invoke_toolkit-0.0.7/src/invoke_toolkit/utils/text.py +14 -0
  24. invoke_toolkit-0.0.7/tasks.py +305 -0
  25. invoke_toolkit-0.0.7/tests/conftest.py +42 -0
  26. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/program/main.py +2 -2
  27. invoke_toolkit-0.0.7/tests/program/tasks/coll1.py +5 -0
  28. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/script/test_script.py +3 -2
  29. invoke_toolkit-0.0.7/tests/tasks/test_extensions_config.py +38 -0
  30. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/test_collection.py +3 -3
  31. invoke_toolkit-0.0.7/tests/test_context_class.py +39 -0
  32. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/test_executor.py +4 -4
  33. invoke_toolkit-0.0.7/tests/test_loader.py +70 -0
  34. invoke_toolkit-0.0.7/tests/test_parsing.py +36 -0
  35. invoke_toolkit-0.0.7/uv.lock +1328 -0
  36. invoke_toolkit-0.0.4/.gitignore +0 -4
  37. invoke_toolkit-0.0.4/common-2.23.0.tgz +0 -0
  38. invoke_toolkit-0.0.4/local_tasks.py +0 -6
  39. invoke_toolkit-0.0.4/src/invoke_toolkit/__init__.py +0 -40
  40. invoke_toolkit-0.0.4/src/invoke_toolkit/collections.py +0 -112
  41. invoke_toolkit-0.0.4/src/invoke_toolkit/context/context.py +0 -34
  42. invoke_toolkit-0.0.4/src/invoke_toolkit/context/types.py +0 -29
  43. invoke_toolkit-0.0.4/src/invoke_toolkit/output/console.py +0 -15
  44. invoke_toolkit-0.0.4/src/invoke_toolkit/program/program.py +0 -104
  45. invoke_toolkit-0.0.4/src/invoke_toolkit/tasks/plugin.py +0 -18
  46. invoke_toolkit-0.0.4/tasks.py +0 -138
  47. invoke_toolkit-0.0.4/tests/conftest.py +0 -15
  48. invoke_toolkit-0.0.4/tests/program/tasks/coll1.py +0 -5
  49. invoke_toolkit-0.0.4/uv.lock +0 -717
  50. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/.envrc +0 -0
  51. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/LICENSE.txt +0 -0
  52. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/README.md +0 -0
  53. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/dist/.gitignore +0 -0
  54. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/__main__.py +0 -0
  55. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/config/__init__.py +0 -0
  56. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/config/status_helper.py +0 -0
  57. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/context/__init__.py +0 -0
  58. {invoke_toolkit-0.0.4/src/invoke_toolkit/log → invoke_toolkit-0.0.7/src/invoke_toolkit/extensions}/__init__.py +0 -0
  59. {invoke_toolkit-0.0.4/src/invoke_toolkit → invoke_toolkit-0.0.7/src/invoke_toolkit/extensions}/tasks/dist.py +0 -0
  60. {invoke_toolkit-0.0.4/src/invoke_toolkit/runners → invoke_toolkit-0.0.7/src/invoke_toolkit/log}/__init__.py +0 -0
  61. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/log/logger.py +0 -0
  62. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/output/__init__.py +0 -0
  63. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/output/utils.py +0 -0
  64. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/program/__init__.py +0 -0
  65. {invoke_toolkit-0.0.4/src/invoke_toolkit/scripts → invoke_toolkit-0.0.7/src/invoke_toolkit/runners}/__init__.py +0 -0
  66. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/runners/rich.py +0 -0
  67. {invoke_toolkit-0.0.4/src/invoke_toolkit/tasks → invoke_toolkit-0.0.7/src/invoke_toolkit/scripts}/__init__.py +0 -0
  68. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/scripts/loader.py +0 -0
  69. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/utils/__init__.py +0 -0
  70. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/src/invoke_toolkit/utils/inspection.py +0 -0
  71. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/__init__.py +0 -0
  72. {invoke_toolkit-0.0.4 → invoke_toolkit-0.0.7}/tests/program/tasks/__init__.py +0 -0
@@ -0,0 +1,9 @@
1
+ .*
2
+ !.envrc
3
+ !.gitignore
4
+ **/__pycache__/*
5
+ **/_site/*
6
+ docs/reference/
7
+ **/.quarto/*
8
+ docs/objects.json
9
+ _tasks.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invoke-toolkit
3
- Version: 0.0.4
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.8
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.8"
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
- dependencies = [
41
- "invoke",
42
- "rich",
43
- "appdirs",
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
- invtk = "invoke_toolkit.program.main:program.run"
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
- "**/tasks.py:dangerous-default-value",
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,7 @@
1
+ """Package namespace imports"""
2
+
3
+ from invoke_toolkit.tasks import task
4
+ from invoke_toolkit.context import InvokeToolkitContext as Context
5
+
6
+
7
+ __all__ = ["task", "Context"]
@@ -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, Optional
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