invoke-toolkit 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ # This is an example configuration to enable detect-secrets in the pre-commit hook.
2
+ # Add this file to the root folder of your repository.
3
+ #
4
+ # Read pre-commit hook framework https://pre-commit.com/ for more details about the structure of config yaml file and how git pre-commit would invoke each hook.
5
+ #
6
+ # This line indicates we will use the hook from ibm/detect-secrets to run scan during committing phase.
7
+ repos:
8
+ - repo: https://github.com/ibm/detect-secrets
9
+ # If you desire to use a specific version of detect-secrets, you can replace `master` with other git revisions such as branch, tag or commit sha.
10
+ # You are encouraged to use static refs such as tags, instead of branch name
11
+ #
12
+ # Running "pre-commit autoupdate" automatically updates rev to latest tag
13
+ rev: 0.13.1+ibm.61.dss
14
+ hooks:
15
+ - id: detect-secrets # pragma: whitelist secret
16
+ # Add options for detect-secrets-hook binary. You can run `detect-secrets-hook --help` to list out all possible options.
17
+ # You may also run `pre-commit run detect-secrets` to preview the scan result.
18
+ # when "--baseline" without "--use-all-plugins", pre-commit scan with just plugins in baseline file
19
+ # when "--baseline" with "--use-all-plugins", pre-commit scan with all available plugins
20
+ # add "--fail-on-unaudited" to fail pre-commit for unaudited potential secrets
21
+ args: [--baseline, .secrets.baseline, --use-all-plugins]
22
+ exclude: 'src/fp_helper/_vendor/.*'
23
+
24
+ - repo: https://github.com/astral-sh/ruff-pre-commit
25
+ # Ruff version.
26
+ rev: v0.1.6
27
+ hooks:
28
+ # Run the linter.
29
+ - id: ruff
30
+ args: [ --fix ]
31
+ exclude: 'src/.*'
32
+ # Run the formatter.
33
+ - id: ruff-format
34
+ # exclude: 'src/.*'
35
+
36
+
37
+ - repo: https://github.com/codespell-project/codespell
38
+ rev: v2.2.4
39
+ hooks:
40
+ - id: codespell
41
+ types_or: [python, rst, markdown, cython, c]
42
+ additional_dependencies: [tomli]
43
+ # exclude: 'src/.*'
@@ -0,0 +1,85 @@
1
+ {
2
+ "exclude": {
3
+ "files": "^.secrets.baseline$",
4
+ "lines": null
5
+ },
6
+ "generated_at": "2024-08-04T19:48:23Z",
7
+ "plugins_used": [
8
+ {
9
+ "name": "AWSKeyDetector"
10
+ },
11
+ {
12
+ "name": "ArtifactoryDetector"
13
+ },
14
+ {
15
+ "name": "AzureStorageKeyDetector"
16
+ },
17
+ {
18
+ "base64_limit": 4.5,
19
+ "name": "Base64HighEntropyString"
20
+ },
21
+ {
22
+ "name": "BasicAuthDetector"
23
+ },
24
+ {
25
+ "name": "BoxDetector"
26
+ },
27
+ {
28
+ "name": "CloudantDetector"
29
+ },
30
+ {
31
+ "ghe_instance": "github.ibm.com",
32
+ "name": "GheDetector"
33
+ },
34
+ {
35
+ "name": "GitHubTokenDetector"
36
+ },
37
+ {
38
+ "hex_limit": 3,
39
+ "name": "HexHighEntropyString"
40
+ },
41
+ {
42
+ "name": "IbmCloudIamDetector"
43
+ },
44
+ {
45
+ "name": "IbmCosHmacDetector"
46
+ },
47
+ {
48
+ "name": "JwtTokenDetector"
49
+ },
50
+ {
51
+ "keyword_exclude": null,
52
+ "name": "KeywordDetector"
53
+ },
54
+ {
55
+ "name": "MailchimpDetector"
56
+ },
57
+ {
58
+ "name": "NpmDetector"
59
+ },
60
+ {
61
+ "name": "PrivateKeyDetector"
62
+ },
63
+ {
64
+ "name": "SlackDetector"
65
+ },
66
+ {
67
+ "name": "SoftlayerDetector"
68
+ },
69
+ {
70
+ "name": "SquareOAuthDetector"
71
+ },
72
+ {
73
+ "name": "StripeDetector"
74
+ },
75
+ {
76
+ "name": "TwilioKeyDetector"
77
+ }
78
+ ],
79
+ "results": {},
80
+ "version": "0.13.1+ibm.61.dss",
81
+ "word_list": {
82
+ "file": null,
83
+ "hash": null
84
+ }
85
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Current File",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "program": "${file}",
12
+ "console": "integratedTerminal"
13
+ },
14
+ {
15
+ "name": "Python Debugger: Test program (tests/program/main)",
16
+ "type": "debugpy",
17
+ "request": "launch",
18
+ "program": "${workspaceFolder}/tests/program/main.py",
19
+ "console": "integratedTerminal",
20
+ "cwd": "${workspaceFolder}/tests/program/"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "workbench.colorCustomizations": {
3
+ "activityBar.activeBackground": "#ba5c56",
4
+ "activityBar.background": "#ba5c56",
5
+ "activityBar.foreground": "#e7e7e7",
6
+ "activityBar.inactiveForeground": "#e7e7e799",
7
+ "activityBarBadge.background": "#77c87c",
8
+ "activityBarBadge.foreground": "#15202b",
9
+ "commandCenter.border": "#e7e7e799",
10
+ "sash.hoverBorder": "#ba5c56",
11
+ "statusBar.background": "#9d4640",
12
+ "statusBar.foreground": "#e7e7e7",
13
+ "statusBarItem.hoverBackground": "#ba5c56",
14
+ "statusBarItem.remoteBackground": "#9d4640",
15
+ "statusBarItem.remoteForeground": "#e7e7e7",
16
+ "titleBar.activeBackground": "#9d4640",
17
+ "titleBar.activeForeground": "#e7e7e7",
18
+ "titleBar.inactiveBackground": "#9d464099",
19
+ "titleBar.inactiveForeground": "#e7e7e799"
20
+ },
21
+ "peacock.color": "#9d4640",
22
+ "ruff.format.preview": true,
23
+ "ruff.lint.preview": true,
24
+ "ruff.showNotifications": "onWarning"
25
+ }
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Nahuel Defossé <nahuel.deofsse@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.3
2
+ Name: invoke-toolkit
3
+ Version: 0.0.1
4
+ Summary: A set of extended APIs for PyInvoke for composable scripts, plugins and richer output
5
+ Project-URL: Documentation, https://github.com/D3f0/invoke-toolkit#readme
6
+ Project-URL: Issues, https://github.com/D3f0/invoke-toolkit/issues
7
+ Project-URL: Source, https://github.com/D3f0/invoke-toolkit
8
+ Author-email: Nahuel Defossé <D3f0@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE.txt
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: MacOS :: MacOS X
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: Unix
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Topic :: Software Development
20
+ Classifier: Topic :: Software Development :: Build Tools
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: System :: Software Distribution
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Requires-Python: >=3.8
26
+ Requires-Dist: invoke
27
+ Requires-Dist: rich
28
+ Description-Content-Type: text/markdown
29
+
30
+ # invoke-toolkit
31
+
32
+ A set of extended APIs for PyInvoke for composable scripts, plugins and richer output.
33
+
34
+ This extends the Collection from Invoke so it can create automatically collections.
35
+
36
+ [![PyPI - Version](https://img.shields.io/pypi/v/invoke-toolkit.svg)](https://pypi.org/project/invoke-toolkit)
37
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/invoke-toolkit.svg)](https://pypi.org/project/invoke-toolkit)
38
+
39
+ -----
40
+
41
+ ## Table of Contents
42
+
43
+ - [invoke-toolkit](#invoke-toolkit)
44
+ - [Table of Contents](#table-of-contents)
45
+ - [Features](#features)
46
+ - [Do I need this package](#do-i-need-this-package)
47
+ - [Installation](#installation)
48
+ - [Development](#development)
49
+ - [License](#license)
50
+
51
+ ## Features
52
+
53
+ - Task discovery by namespace for extendable/composable CLIs
54
+ - Discovery to *plain old* tasks.py (or any other name)
55
+ - Integration with stand alone binaries for specific tasks
56
+ - **Future** Download binaries
57
+
58
+ ## Do I need this package
59
+
60
+ If you have...
61
+
62
+ - Used `invoke` for a while and...
63
+ - Have a large `tasks.py` that needs to be modularized
64
+ - Have a lot of copy/pasted code in multiple `tasks.py` across multiple repos.
65
+ - Have exceeded the approach of a repository cloned as `~/tasks/` with more .py files that you want to manage.
66
+ - Or you want to combine various tasks defined in multiple directories
67
+ - You want to create a zipped (shiv) redistribute script for container environments
68
+ like Kubernetes based CI environments with only requiring the Python interpreter.
69
+
70
+ ## Installation
71
+
72
+ ```console
73
+ pip install invoke-toolkit
74
+ ```
75
+
76
+ ## Development
77
+
78
+ This project utilizes the `pre-commit` framework, make sure you run:
79
+
80
+ `pre-commit install`
81
+
82
+ ## License
83
+
84
+ `invoke-toolkit` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,55 @@
1
+ # invoke-toolkit
2
+
3
+ A set of extended APIs for PyInvoke for composable scripts, plugins and richer output.
4
+
5
+ This extends the Collection from Invoke so it can create automatically collections.
6
+
7
+ [![PyPI - Version](https://img.shields.io/pypi/v/invoke-toolkit.svg)](https://pypi.org/project/invoke-toolkit)
8
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/invoke-toolkit.svg)](https://pypi.org/project/invoke-toolkit)
9
+
10
+ -----
11
+
12
+ ## Table of Contents
13
+
14
+ - [invoke-toolkit](#invoke-toolkit)
15
+ - [Table of Contents](#table-of-contents)
16
+ - [Features](#features)
17
+ - [Do I need this package](#do-i-need-this-package)
18
+ - [Installation](#installation)
19
+ - [Development](#development)
20
+ - [License](#license)
21
+
22
+ ## Features
23
+
24
+ - Task discovery by namespace for extendable/composable CLIs
25
+ - Discovery to *plain old* tasks.py (or any other name)
26
+ - Integration with stand alone binaries for specific tasks
27
+ - **Future** Download binaries
28
+
29
+ ## Do I need this package
30
+
31
+ If you have...
32
+
33
+ - Used `invoke` for a while and...
34
+ - Have a large `tasks.py` that needs to be modularized
35
+ - Have a lot of copy/pasted code in multiple `tasks.py` across multiple repos.
36
+ - Have exceeded the approach of a repository cloned as `~/tasks/` with more .py files that you want to manage.
37
+ - Or you want to combine various tasks defined in multiple directories
38
+ - You want to create a zipped (shiv) redistribute script for container environments
39
+ like Kubernetes based CI environments with only requiring the Python interpreter.
40
+
41
+ ## Installation
42
+
43
+ ```console
44
+ pip install invoke-toolkit
45
+ ```
46
+
47
+ ## Development
48
+
49
+ This project utilizes the `pre-commit` framework, make sure you run:
50
+
51
+ `pre-commit install`
52
+
53
+ ## License
54
+
55
+ `invoke-toolkit` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "invoke-toolkit"
7
+ dynamic = ["version"]
8
+ description = "A set of extended APIs for PyInvoke for composable scripts, plugins and richer output"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ keywords = []
13
+ authors = [
14
+ { name = "Nahuel Defossé", email = "D3f0@users.noreply.github.com" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: Implementation :: CPython",
22
+ "Operating System :: POSIX",
23
+ "Operating System :: Unix",
24
+ "Operating System :: MacOS :: MacOS X",
25
+ "Topic :: Software Development",
26
+ "Topic :: Software Development :: Build Tools",
27
+ "Topic :: Software Development :: Libraries",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ "Topic :: System :: Software Distribution",
30
+ "Topic :: System :: Systems Administration",
31
+ ]
32
+
33
+ dependencies = [
34
+ "invoke",
35
+ "rich"
36
+ ]
37
+
38
+ [project.urls]
39
+ Documentation = "https://github.com/D3f0/invoke-toolkit#readme"
40
+ Issues = "https://github.com/D3f0/invoke-toolkit/issues"
41
+ Source = "https://github.com/D3f0/invoke-toolkit"
42
+
43
+ [tool.hatch.version]
44
+ path = "src/invoke_toolkit/__about__.py"
45
+
46
+ [tool.hatch.envs.types]
47
+ extra-dependencies = [
48
+ "mypy>=1.0.0",
49
+ ]
50
+ [tool.hatch.envs.types.scripts]
51
+ check = "mypy --install-types --non-interactive {args:src/invoke_toolkit tests}"
52
+
53
+ [tool.coverage.run]
54
+ source_pkgs = ["invoke_toolkit", "tests"]
55
+ branch = true
56
+ parallel = true
57
+ omit = [
58
+ "src/invoke_toolkit/__about__.py",
59
+ ]
60
+
61
+ [tool.coverage.paths]
62
+ invoke_toolkit = ["src/invoke_toolkit", "*/invoke-toolkit/src/invoke_toolkit"]
63
+ tests = ["tests", "*/invoke-toolkit/tests"]
64
+
65
+ [tool.coverage.report]
66
+ exclude_lines = [
67
+ "no cov",
68
+ "if __name__ == .__main__.:",
69
+ "if TYPE_CHECKING:",
70
+ ]
71
+
72
+
73
+ [tool.hatch.envs.dev]
74
+ dependencies = [
75
+ # Invoke's original set of tasks
76
+ "pytest",
77
+ "pytest-virtualenv",
78
+ "coverage",
79
+ "pdbpp",
80
+ ]
81
+ [tool.hatch.envs.dev.scripts]
82
+ run-coverage = "pytest --cov-config=pyproject.toml --cov=pkg --cov=tests"
83
+ run = "run-coverage --no-cov"
84
+ test = "pytest -v {}"
85
+ testdbg = "pytest -v --pdb {}"
86
+
87
+ [tool.hatch.envs.docs]
88
+ dependencies = [
89
+ "sphinx"
90
+ ]
91
+
92
+
93
+ [tool.ruff.lint.per-file-ignores]
94
+ "*tasks*" = ["ARG001"]
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2024-present Nahuel Defossé <nahuel.deofsse@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "0.0.1"
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2024-present Nahuel Defossé <nahuel.deofsse@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,100 @@
1
+ import importlib
2
+ import pkgutil
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+ from typing import Dict, Union
7
+
8
+ from invoke.collection import Collection as InvokeCollection
9
+ from invoke.util import debug
10
+
11
+ from invoke_toolkit.utils.inspection import get_calling_file_path
12
+
13
+
14
+ class CollectionError(Exception):
15
+ """Base class for import discovery errors"""
16
+
17
+
18
+ class CollectionNotImportedError(CollectionError):
19
+ ...
20
+
21
+
22
+ def import_submodules(package_name: str) -> Dict[str, ModuleType]:
23
+ """
24
+ Import all submodules of a module from an imported module
25
+
26
+ :param package_name: Package name
27
+ :type package_name: str
28
+ :rtype: dict[types.ModuleType]
29
+ """
30
+ debug("Importing submodules in %s", package_name)
31
+ try:
32
+ package = sys.modules[package_name]
33
+ except ImportError as import_error:
34
+ msg = f"Module {package_name} not imported"
35
+ raise CollectionNotImportedError(msg) from import_error
36
+ result = {}
37
+ for _loader, name, _is_pkg in pkgutil.walk_packages(package.__path__):
38
+ try:
39
+ result[name] = importlib.import_module(package_name + "." + name)
40
+ except (ImportError, SyntaxError) as error:
41
+ if not name.startswith("__"):
42
+ debug(f"Error loading {name}: {error}")
43
+ else:
44
+ debug(f"Error loading {name}: {error}")
45
+
46
+ return result
47
+
48
+
49
+ class Collection(InvokeCollection):
50
+ """
51
+ This Collection allows to load sub-collections from python package paths/namespaces
52
+ like `myscripts.tasks.*`
53
+ """
54
+
55
+ def add_collections_from_namespace(self, namespace: str) -> bool:
56
+ """Iterates over a namespace and imports the submodules"""
57
+ # Attempt simple import
58
+ ok = False
59
+ if namespace not in sys.modules:
60
+ debug(f"Attempting simple import of {namespace}")
61
+ try:
62
+ importlib.import_module(namespace)
63
+ ok = True
64
+ except ImportError:
65
+ debug(f"Failed to import {namespace}")
66
+
67
+ if not ok:
68
+ debug("Starting stack inspection to find module")
69
+ # Trying to import relative to caller's script
70
+ caller_path = get_calling_file_path(
71
+ # We're going to get the path of the file where this call
72
+ # was made
73
+ find_call_text=".add_collections_from_namespace("
74
+ )
75
+ debug(f"Adding {caller_path} in order to import {namespace}")
76
+ sys.path.append(caller_path)
77
+ # This should work even if there's no __init__ alongside the
78
+ # program main
79
+ importlib.import_module(namespace)
80
+
81
+ for name, module in import_submodules(namespace).items():
82
+ coll = Collection.from_module(module)
83
+ self.add_collection(coll=coll, name=name)
84
+
85
+ def load_plugins(self):
86
+ ...
87
+
88
+ def load_directory(self, directory: Union[str, Path]) -> None:
89
+ """Loads tasks from a folder"""
90
+ if isinstance(directory, str):
91
+ path = Path(directory)
92
+ elif not isinstance(directory, Path):
93
+ msg = f"The directory to load plugins is not a str/Path: {directory}:{type(directory)}"
94
+ raise TypeError(msg)
95
+ else:
96
+ path = directory
97
+
98
+ existing_paths = {pth for pth in sys.path if Path(pth).is_dir()}
99
+ if path not in existing_paths:
100
+ sys.path.append(str(path))
@@ -0,0 +1,9 @@
1
+ from invoke import Context, task
2
+
3
+
4
+ @task(default=True)
5
+ def install(ctx: Context):
6
+ """
7
+ Installation of Python completions.
8
+ This is a re-implementation of inv[oke] --print-completion-script
9
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ Generate self contained distributable scripts
3
+ """
@@ -0,0 +1,16 @@
1
+ from invoke import Context, task
2
+
3
+
4
+ @task(default=True)
5
+ def list_(ctx: Context):
6
+ """List plugins"""
7
+
8
+
9
+ @task(default=True)
10
+ def add(ctx: Context, plugin_spec: str) -> None:
11
+ """Add a plugin"""
12
+
13
+
14
+ @task(default=True)
15
+ def remove(ctx: Context, name: str) -> None:
16
+ """Add a plugin"""
@@ -0,0 +1,41 @@
1
+ import inspect
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from invoke.util import debug
7
+ from rich import print
8
+
9
+ # def print_call_stack():
10
+ # """Prints the current call stack."""
11
+ # for frame_info in inspect.stack():
12
+ # filename, lineno, function, code_context, index = frame_info
13
+ # print(f"File: {filename}, Line: {lineno}, Function: {function}")
14
+
15
+
16
+ def print_rich_frames(frames: list):
17
+ print(frames, file=sys.stderr)
18
+
19
+
20
+ def get_calling_file_path(find_call_text: str) -> Optional[str]:
21
+ """Returns the containing folder of the module where the find_call_text is located"""
22
+
23
+ # Get the frame object of the caller
24
+ start_offset = 2
25
+ stack = inspect.stack()
26
+ index_frame_dict = dict(enumerate(stack[start_offset:]))
27
+ # print_rich_frames(stack[:2])
28
+ # ...
29
+ # print_rich_frames(index_frame_dict)
30
+ frame = None
31
+ found = False
32
+ for i, frame in index_frame_dict.items():
33
+ if any(find_call_text in line for line in frame.code_context):
34
+ debug(f"Found '{find_call_text}' in {frame}, call offset {i+2}")
35
+ found = True
36
+ break
37
+ if not found:
38
+ return None
39
+ # Get the module object of the caller
40
+ containing_directory = str(Path(frame.filename).parent.parent)
41
+ return containing_directory
@@ -0,0 +1,24 @@
1
+ from invoke import task, Context
2
+ import subprocess
3
+
4
+ REPO_ROOT = (
5
+ subprocess.check_output("git rev-parse --show-toplevel", shell="sh")
6
+ .strip()
7
+ .decode()
8
+ )
9
+
10
+
11
+ @task()
12
+ def build(ctx: Context):
13
+ ctx.run("hatch build", pty=True)
14
+
15
+
16
+ @task()
17
+ def test(ctx: Context):
18
+ ctx.run("hatch run dev:pytest", pty=True)
19
+
20
+
21
+ @task()
22
+ def clean_dist(ctx: Context):
23
+ with ctx.cd(REPO_ROOT):
24
+ ctx.run("dist/*")
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2024-present Nahuel Defossé <nahuel.deofsse@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,16 @@
1
+ from invoke.program import Program
2
+
3
+ from invoke_toolkit.collections import Collection
4
+
5
+
6
+ class TestProgram:
7
+ ...
8
+
9
+
10
+ ns = Collection()
11
+ ns.add_collections_from_namespace("program.tasks")
12
+ program = Program(name="test program", version="0.0.1", namespace=ns)
13
+
14
+
15
+ if __name__ == "__main__":
16
+ program.run()
File without changes
@@ -0,0 +1,6 @@
1
+ from invoke import task
2
+
3
+
4
+ @task()
5
+ def task1(c):
6
+ ...
@@ -0,0 +1,39 @@
1
+ import importlib
2
+ import pkgutil
3
+ import sys
4
+ import types
5
+ from unittest import mock
6
+
7
+ from invoke_toolkit.collections import Collection
8
+
9
+
10
+ def my_function(package_path):
11
+ modules = []
12
+ for _, module_name, _ in pkgutil.walk_packages([package_path]):
13
+ modules.append(module_name)
14
+ return modules
15
+
16
+
17
+ def test_collection_load_submodules(monkeypatch):
18
+ ns = Collection()
19
+
20
+ def mock_walk_packages(path):
21
+ # Simulate modules
22
+ module1 = types.ModuleType("not_to_import")
23
+ module2 = types.ModuleType("to_import.tasks.mod1")
24
+ module3 = types.ModuleType("to_import.tasks.mod2")
25
+ return [
26
+ ("module1", "", module1),
27
+ ("to_import.tasks.mod1", "", module2),
28
+ ("to_import.tasks.mod2", "", module3),
29
+ ]
30
+
31
+ def mock_import_module(name):
32
+ monkeypatch.setitem(name, types.ModuleType(name=name))
33
+
34
+ monkeypatch.setattr(importlib, "import_module", mock.MagicMock())
35
+ monkeypatch.setattr(pkgutil, "walk_packages", mock_walk_packages)
36
+ module_to_import = types.ModuleType(name="to_import")
37
+ monkeypatch.setitem(sys.modules, "to_import.tasks", module_to_import)
38
+ result = ns.add_collections_from_namespace("to_import.tasks")
39
+ breakpoint()