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.
@@ -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))
File without changes
@@ -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"""
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
+ [![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,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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.