invoke-toolkit 0.0.1__py3-none-any.whl
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/__about__.py +4 -0
- invoke_toolkit/__init__.py +3 -0
- invoke_toolkit/collections.py +100 -0
- invoke_toolkit/tasks/__init__.py +0 -0
- invoke_toolkit/tasks/completion.py +9 -0
- invoke_toolkit/tasks/dist.py +3 -0
- invoke_toolkit/tasks/plugin.py +16 -0
- invoke_toolkit/utils/__init__.py +0 -0
- invoke_toolkit/utils/inspection.py +41 -0
- invoke_toolkit-0.0.1.dist-info/METADATA +84 -0
- invoke_toolkit-0.0.1.dist-info/RECORD +13 -0
- invoke_toolkit-0.0.1.dist-info/WHEEL +4 -0
- invoke_toolkit-0.0.1.dist-info/licenses/LICENSE.txt +9 -0
|
@@ -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))
|
|
File without changes
|
|
@@ -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"""
|
|
File without changes
|
|
@@ -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,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
|
+
[](https://pypi.org/project/invoke-toolkit)
|
|
37
|
+
[](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,13 @@
|
|
|
1
|
+
invoke_toolkit/__about__.py,sha256=J9Xj2pR1freidE7fZSQD6Ed8zPApk6ShPcF-mEMst8A,137
|
|
2
|
+
invoke_toolkit/__init__.py,sha256=VmURM0CUIJkCXcSZY_2ehZ6wv0QUMJWlWotTBhaEwKk,115
|
|
3
|
+
invoke_toolkit/collections.py,sha256=ZqDCjFgn6XiJxSbeWEjDijOyB9KBLPlZgL6RntAmgrI,3416
|
|
4
|
+
invoke_toolkit/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
invoke_toolkit/tasks/completion.py,sha256=1hPiUI67K1ZGUqAPPXgmZKCHSueAioHWXlAkALlT-jE,208
|
|
6
|
+
invoke_toolkit/tasks/dist.py,sha256=v1_zCzU0AvjeuyT1M_ob3Ywe6iPrHc01XNNzvwUtCCg,54
|
|
7
|
+
invoke_toolkit/tasks/plugin.py,sha256=lgS6P8LJWZSja7-7SPHh3_dn2yh8kluBaZPVd5ssztY,287
|
|
8
|
+
invoke_toolkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
invoke_toolkit/utils/inspection.py,sha256=kk2Q0gitFZm6xYU94TwikyQunz2Tr1UBy6yK8qV9mks,1311
|
|
10
|
+
invoke_toolkit-0.0.1.dist-info/METADATA,sha256=AhxWA1lhsXPuD6TiilkTBVaHp3nCrTx4g1lSchgfbAY,3051
|
|
11
|
+
invoke_toolkit-0.0.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
12
|
+
invoke_toolkit-0.0.1.dist-info/licenses/LICENSE.txt,sha256=vWoqN4WWclgZWeE_wLxIWaK34LZ8yLDsIqd5U3cQRBw,1107
|
|
13
|
+
invoke_toolkit-0.0.1.dist-info/RECORD,,
|
|
@@ -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.
|