module-runner 0.1.0__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.
- module_runner-0.1.0/LICENSE +21 -0
- module_runner-0.1.0/PKG-INFO +150 -0
- module_runner-0.1.0/README.md +140 -0
- module_runner-0.1.0/module_runner/__init__.py +7 -0
- module_runner-0.1.0/module_runner/environment/__init__.py +4 -0
- module_runner-0.1.0/module_runner/environment/constants.py +4 -0
- module_runner-0.1.0/module_runner/environment/manager.py +40 -0
- module_runner-0.1.0/module_runner/environment/mode.py +8 -0
- module_runner-0.1.0/module_runner/environment/pip_env.py +60 -0
- module_runner-0.1.0/module_runner/environment/resolver.py +50 -0
- module_runner-0.1.0/module_runner/environment/utils.py +9 -0
- module_runner-0.1.0/module_runner/environment/uv_env.py +60 -0
- module_runner-0.1.0/module_runner/environments.py +0 -0
- module_runner-0.1.0/module_runner/exceptions.py +33 -0
- module_runner-0.1.0/module_runner/runner.py +60 -0
- module_runner-0.1.0/module_runner.egg-info/PKG-INFO +150 -0
- module_runner-0.1.0/module_runner.egg-info/SOURCES.txt +19 -0
- module_runner-0.1.0/module_runner.egg-info/dependency_links.txt +1 -0
- module_runner-0.1.0/module_runner.egg-info/top_level.txt +1 -0
- module_runner-0.1.0/pyproject.toml +15 -0
- module_runner-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: module-runner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight helper that prepares Python environments and executes standalone modules.
|
|
5
|
+
Author: Leo Jaimesson
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
# Module Runner
|
|
12
|
+
|
|
13
|
+
Module Runner sits in the gap between ad-hoc scripts and heavyweight orchestration frameworks. It gives each module a predictable, isolated environment without asking you to bolt on extra infrastructure, schedulers, or workflow engines. The project stays intentionally lightweight and focuses on two responsibilities:
|
|
14
|
+
|
|
15
|
+
1. Detect the proper execution strategy (system Python, `venv`, or `uv`).
|
|
16
|
+
2. Launch the module with an optional JSON payload, returning the `subprocess.CompletedProcess` so you can decide how to consume `stdout`, `stderr`, or exit codes.
|
|
17
|
+
|
|
18
|
+
Any higher-level orchestration (pipelines, RPC, shared storage, etc.) is an application concern, keeping this library small and predictable.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- ⚙️ **Automatic environment discovery**: detects `pyproject.toml` or `requirements.txt` (→ `uv` when available, `venv` as fallback), or falls back to the current interpreter.
|
|
23
|
+
- 📦 **Per-module dependencies**: each module lives in its own folder with its own toolchain, avoiding cross-contamination.
|
|
24
|
+
- 🧱 **Opinionated boundary**: no implicit payload chaining—what a module prints, writes, or exposes is entirely up to you.
|
|
25
|
+
- 🪪 **Clear error surface**: `RunnerExecutionError` captures module failures while missing tooling raises descriptive `RuntimeError`s, keeping debugging straightforward.
|
|
26
|
+
- 🔍 **Built-in logging**: uses Python's standard `logging` module under the `module_runner` logger — enable `DEBUG` to see the resolved strategy, every setup command, and the final execution command.
|
|
27
|
+
|
|
28
|
+
## Logging
|
|
29
|
+
|
|
30
|
+
Module Runner emits `INFO`-level log records under the `module_runner` logger. To see them:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import logging
|
|
34
|
+
logging.getLogger("module_runner").setLevel(logging.INFO)
|
|
35
|
+
logging.basicConfig()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Example output:
|
|
39
|
+
```
|
|
40
|
+
[normalize] strategy=pip (requirements.txt detected, uv unavailable — fallback to venv+pip)
|
|
41
|
+
[normalize] creating venv: /usr/bin/python3 -m venv .venv
|
|
42
|
+
[normalize] installing deps: .venv/bin/python -m pip install -r requirements.txt
|
|
43
|
+
[normalize] executing: .venv/bin/python main.py {"text": " Hello World "}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Why This Exists
|
|
47
|
+
|
|
48
|
+
Module Runner is the missing middle layer between one-off helper scripts and enterprise orchestrators. It gives you just enough structure to keep automation tidy while remaining lightweight, infrastructure-free, and dependency-light.
|
|
49
|
+
|
|
50
|
+
## When to Use
|
|
51
|
+
|
|
52
|
+
Use Module Runner when you:
|
|
53
|
+
|
|
54
|
+
- Need simple local automation or scripting glue with a lightweight footprint
|
|
55
|
+
- Have dependency conflicts between scripts and want per-module isolation
|
|
56
|
+
- Want predictable, sequential execution you can reason about
|
|
57
|
+
- Prefer to stay container-free for lightweight tasks
|
|
58
|
+
- Do not need a workflow engine or task scheduler
|
|
59
|
+
|
|
60
|
+
## When Not to Use
|
|
61
|
+
|
|
62
|
+
Reach for other tooling if you require:
|
|
63
|
+
|
|
64
|
+
- Complex DAG dependencies or branching workflows
|
|
65
|
+
- Scheduling, cron-style orchestration, or SLAs
|
|
66
|
+
- Distributed workers or autoscaling fleets
|
|
67
|
+
- Monitoring dashboards, retries, or alerting UI
|
|
68
|
+
- Enterprise workflow/orchestration guarantees
|
|
69
|
+
|
|
70
|
+
## Philosophy
|
|
71
|
+
|
|
72
|
+
- Small, explicit, predictable, and lightweight
|
|
73
|
+
- Infrastructure-free: relies on the Python already on your machine
|
|
74
|
+
- Focused on one problem—launching modules in clean environments
|
|
75
|
+
|
|
76
|
+
It is not meant to be a workflow engine; it simply keeps isolated scripts manageable.
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# using pip
|
|
82
|
+
pip install module_runner
|
|
83
|
+
|
|
84
|
+
# using uv
|
|
85
|
+
uv add module_runner
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You only need Python 3.9+ and whichever tooling your modules request (e.g., `uv`, `venv`, system packages).
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from module_runner import Runner
|
|
95
|
+
|
|
96
|
+
runner = Runner(module_path="modules/normalize_text")
|
|
97
|
+
|
|
98
|
+
process = runner.run(
|
|
99
|
+
payload={"text": " Hello World "},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
print(process.stdout) # or json.loads(process.stdout)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Your module layout needs a `main.py` entry point. Any payload you pass is serialized as JSON and delivered as the last CLI argument. How you emit results (stdout, files, sockets) is entirely your call.
|
|
106
|
+
|
|
107
|
+
## Environment Selection
|
|
108
|
+
|
|
109
|
+
| Signal in module folder | Mode used | Requirement |
|
|
110
|
+
| ----------------------- | ------------- | ----------- |
|
|
111
|
+
| `pyproject.toml` | `uv run ...` | `uv` available in `PATH` |
|
|
112
|
+
| `pyproject.toml` (no `uv`) | `python -m venv` + `pip install .` | `venv` module available |
|
|
113
|
+
| `requirements.txt` | `uv venv` + `uv pip install -r requirements.txt` | `uv` available in `PATH` |
|
|
114
|
+
| `requirements.txt` (no `uv`) | `python -m venv` + `pip install -r requirements.txt` | `venv` module available |
|
|
115
|
+
| none of the above | current interpreter (`sys.executable`) | none |
|
|
116
|
+
|
|
117
|
+
- Auto-detection always checks whether the required tooling is installed. When `uv` is available it is preferred for both `pyproject.toml` and `requirements.txt` modules. If `uv` is not available, both `pyproject.toml` and `requirements.txt` modules fall back to `venv`/`pip`.
|
|
118
|
+
- If a module signals that it needs `uv` or `venv` but the corresponding tooling is missing, Module Runner raises a descriptive `RuntimeError` explaining what needs to be installed.
|
|
119
|
+
- Runtime failures propagate as `RunnerExecutionError`, exposing the module name, exit code, captured `stderr`, captured `stdout`, and the exact `cmd` list that was executed — making it straightforward to reproduce or log failures.
|
|
120
|
+
|
|
121
|
+
## Sandbox Playground
|
|
122
|
+
|
|
123
|
+
The [sandbox](sandbox/README.md) directory ships with two toy modules (`normalize` and `stats`) that showcase:
|
|
124
|
+
|
|
125
|
+
- How `requirements.txt` triggers a module-specific virtual environment.
|
|
126
|
+
- How `pyproject.toml` causes execution via `uv run`.
|
|
127
|
+
- How you can manually chain modules by parsing the `stdout` from the first run and feeding it into the next.
|
|
128
|
+
|
|
129
|
+
Run everything with:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python sandbox/run_examples.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use this folder as a template to create your own modules or to test different deployment scenarios.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/<seu-usuario>/module_runner.git
|
|
141
|
+
cd module_runner
|
|
142
|
+
python -m venv .venv && source .venv/bin/activate
|
|
143
|
+
pip install -e .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Feel free to open issues or pull requests with improvements. The scope intentionally stays small: reliable environment setup and process execution.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
Module Runner is released under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Module Runner
|
|
2
|
+
|
|
3
|
+
Module Runner sits in the gap between ad-hoc scripts and heavyweight orchestration frameworks. It gives each module a predictable, isolated environment without asking you to bolt on extra infrastructure, schedulers, or workflow engines. The project stays intentionally lightweight and focuses on two responsibilities:
|
|
4
|
+
|
|
5
|
+
1. Detect the proper execution strategy (system Python, `venv`, or `uv`).
|
|
6
|
+
2. Launch the module with an optional JSON payload, returning the `subprocess.CompletedProcess` so you can decide how to consume `stdout`, `stderr`, or exit codes.
|
|
7
|
+
|
|
8
|
+
Any higher-level orchestration (pipelines, RPC, shared storage, etc.) is an application concern, keeping this library small and predictable.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- ⚙️ **Automatic environment discovery**: detects `pyproject.toml` or `requirements.txt` (→ `uv` when available, `venv` as fallback), or falls back to the current interpreter.
|
|
13
|
+
- 📦 **Per-module dependencies**: each module lives in its own folder with its own toolchain, avoiding cross-contamination.
|
|
14
|
+
- 🧱 **Opinionated boundary**: no implicit payload chaining—what a module prints, writes, or exposes is entirely up to you.
|
|
15
|
+
- 🪪 **Clear error surface**: `RunnerExecutionError` captures module failures while missing tooling raises descriptive `RuntimeError`s, keeping debugging straightforward.
|
|
16
|
+
- 🔍 **Built-in logging**: uses Python's standard `logging` module under the `module_runner` logger — enable `DEBUG` to see the resolved strategy, every setup command, and the final execution command.
|
|
17
|
+
|
|
18
|
+
## Logging
|
|
19
|
+
|
|
20
|
+
Module Runner emits `INFO`-level log records under the `module_runner` logger. To see them:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import logging
|
|
24
|
+
logging.getLogger("module_runner").setLevel(logging.INFO)
|
|
25
|
+
logging.basicConfig()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Example output:
|
|
29
|
+
```
|
|
30
|
+
[normalize] strategy=pip (requirements.txt detected, uv unavailable — fallback to venv+pip)
|
|
31
|
+
[normalize] creating venv: /usr/bin/python3 -m venv .venv
|
|
32
|
+
[normalize] installing deps: .venv/bin/python -m pip install -r requirements.txt
|
|
33
|
+
[normalize] executing: .venv/bin/python main.py {"text": " Hello World "}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Why This Exists
|
|
37
|
+
|
|
38
|
+
Module Runner is the missing middle layer between one-off helper scripts and enterprise orchestrators. It gives you just enough structure to keep automation tidy while remaining lightweight, infrastructure-free, and dependency-light.
|
|
39
|
+
|
|
40
|
+
## When to Use
|
|
41
|
+
|
|
42
|
+
Use Module Runner when you:
|
|
43
|
+
|
|
44
|
+
- Need simple local automation or scripting glue with a lightweight footprint
|
|
45
|
+
- Have dependency conflicts between scripts and want per-module isolation
|
|
46
|
+
- Want predictable, sequential execution you can reason about
|
|
47
|
+
- Prefer to stay container-free for lightweight tasks
|
|
48
|
+
- Do not need a workflow engine or task scheduler
|
|
49
|
+
|
|
50
|
+
## When Not to Use
|
|
51
|
+
|
|
52
|
+
Reach for other tooling if you require:
|
|
53
|
+
|
|
54
|
+
- Complex DAG dependencies or branching workflows
|
|
55
|
+
- Scheduling, cron-style orchestration, or SLAs
|
|
56
|
+
- Distributed workers or autoscaling fleets
|
|
57
|
+
- Monitoring dashboards, retries, or alerting UI
|
|
58
|
+
- Enterprise workflow/orchestration guarantees
|
|
59
|
+
|
|
60
|
+
## Philosophy
|
|
61
|
+
|
|
62
|
+
- Small, explicit, predictable, and lightweight
|
|
63
|
+
- Infrastructure-free: relies on the Python already on your machine
|
|
64
|
+
- Focused on one problem—launching modules in clean environments
|
|
65
|
+
|
|
66
|
+
It is not meant to be a workflow engine; it simply keeps isolated scripts manageable.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# using pip
|
|
72
|
+
pip install module_runner
|
|
73
|
+
|
|
74
|
+
# using uv
|
|
75
|
+
uv add module_runner
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
You only need Python 3.9+ and whichever tooling your modules request (e.g., `uv`, `venv`, system packages).
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from module_runner import Runner
|
|
85
|
+
|
|
86
|
+
runner = Runner(module_path="modules/normalize_text")
|
|
87
|
+
|
|
88
|
+
process = runner.run(
|
|
89
|
+
payload={"text": " Hello World "},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
print(process.stdout) # or json.loads(process.stdout)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Your module layout needs a `main.py` entry point. Any payload you pass is serialized as JSON and delivered as the last CLI argument. How you emit results (stdout, files, sockets) is entirely your call.
|
|
96
|
+
|
|
97
|
+
## Environment Selection
|
|
98
|
+
|
|
99
|
+
| Signal in module folder | Mode used | Requirement |
|
|
100
|
+
| ----------------------- | ------------- | ----------- |
|
|
101
|
+
| `pyproject.toml` | `uv run ...` | `uv` available in `PATH` |
|
|
102
|
+
| `pyproject.toml` (no `uv`) | `python -m venv` + `pip install .` | `venv` module available |
|
|
103
|
+
| `requirements.txt` | `uv venv` + `uv pip install -r requirements.txt` | `uv` available in `PATH` |
|
|
104
|
+
| `requirements.txt` (no `uv`) | `python -m venv` + `pip install -r requirements.txt` | `venv` module available |
|
|
105
|
+
| none of the above | current interpreter (`sys.executable`) | none |
|
|
106
|
+
|
|
107
|
+
- Auto-detection always checks whether the required tooling is installed. When `uv` is available it is preferred for both `pyproject.toml` and `requirements.txt` modules. If `uv` is not available, both `pyproject.toml` and `requirements.txt` modules fall back to `venv`/`pip`.
|
|
108
|
+
- If a module signals that it needs `uv` or `venv` but the corresponding tooling is missing, Module Runner raises a descriptive `RuntimeError` explaining what needs to be installed.
|
|
109
|
+
- Runtime failures propagate as `RunnerExecutionError`, exposing the module name, exit code, captured `stderr`, captured `stdout`, and the exact `cmd` list that was executed — making it straightforward to reproduce or log failures.
|
|
110
|
+
|
|
111
|
+
## Sandbox Playground
|
|
112
|
+
|
|
113
|
+
The [sandbox](sandbox/README.md) directory ships with two toy modules (`normalize` and `stats`) that showcase:
|
|
114
|
+
|
|
115
|
+
- How `requirements.txt` triggers a module-specific virtual environment.
|
|
116
|
+
- How `pyproject.toml` causes execution via `uv run`.
|
|
117
|
+
- How you can manually chain modules by parsing the `stdout` from the first run and feeding it into the next.
|
|
118
|
+
|
|
119
|
+
Run everything with:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
python sandbox/run_examples.py
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Use this folder as a template to create your own modules or to test different deployment scenarios.
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/<seu-usuario>/module_runner.git
|
|
131
|
+
cd module_runner
|
|
132
|
+
python -m venv .venv && source .venv/bin/activate
|
|
133
|
+
pip install -e .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Feel free to open issues or pull requests with improvements. The scope intentionally stays small: reliable environment setup and process execution.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
Module Runner is released under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .mode import EnvironmentMode
|
|
5
|
+
from .resolver import resolve_mode
|
|
6
|
+
from .pip_env import ensure_pip_environment
|
|
7
|
+
from .uv_env import ensure_uv_environment
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EnvironmentManager:
|
|
11
|
+
|
|
12
|
+
def __init__(self, module_path: Path, mode: EnvironmentMode | str = EnvironmentMode.AUTO):
|
|
13
|
+
self.module_path = module_path
|
|
14
|
+
self.module_name = module_path.name
|
|
15
|
+
self.mode = self._coerce_mode(mode)
|
|
16
|
+
|
|
17
|
+
def _coerce_mode(self, mode: EnvironmentMode | str) -> EnvironmentMode:
|
|
18
|
+
if isinstance(mode, EnvironmentMode):
|
|
19
|
+
return mode
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
return EnvironmentMode(mode)
|
|
23
|
+
except ValueError as exc:
|
|
24
|
+
raise RuntimeError(
|
|
25
|
+
f"Unsupported environment mode '{mode}' for module '{self.module_name}'"
|
|
26
|
+
) from exc
|
|
27
|
+
|
|
28
|
+
def resolve(self) -> EnvironmentMode:
|
|
29
|
+
return resolve_mode(self.module_path, self.mode, self.module_name)
|
|
30
|
+
|
|
31
|
+
def ensure(self) -> Path:
|
|
32
|
+
env_type = self.resolve()
|
|
33
|
+
|
|
34
|
+
if env_type is EnvironmentMode.UV:
|
|
35
|
+
return ensure_uv_environment(self.module_path, self.module_name)
|
|
36
|
+
|
|
37
|
+
if env_type is EnvironmentMode.PIP:
|
|
38
|
+
return ensure_pip_environment(self.module_path, self.module_name)
|
|
39
|
+
|
|
40
|
+
return Path(sys.executable)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import logging
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .constants import PYPROJECT_TOML, REQUIREMENTS_TXT, VENV_DIR
|
|
8
|
+
from .utils import venv_python
|
|
9
|
+
from .mode import EnvironmentMode
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("module_runner")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ensure_pip_environment(module_path: Path, module_name: str) -> Path:
|
|
15
|
+
"""Provision and return the Python interpreter inside a venv."""
|
|
16
|
+
if importlib.util.find_spec("venv") is None:
|
|
17
|
+
detail = "pip mode requires the standard library venv module, but it is unavailable"
|
|
18
|
+
raise RuntimeError(
|
|
19
|
+
f"Environment '{EnvironmentMode.PIP.value}' is unavailable for module '{module_name}': {detail}"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
venv_path = module_path / VENV_DIR
|
|
23
|
+
python_bin = venv_python(venv_path)
|
|
24
|
+
|
|
25
|
+
if not venv_path.exists():
|
|
26
|
+
create_cmd = [sys.executable, "-m", "venv", str(venv_path)]
|
|
27
|
+
logger.info("[%s] creating venv: %s", module_name, " ".join(create_cmd))
|
|
28
|
+
_run_checked(create_cmd, module_path, module_name, "create virtual environment")
|
|
29
|
+
|
|
30
|
+
pyproject = module_path / PYPROJECT_TOML
|
|
31
|
+
req = module_path / REQUIREMENTS_TXT
|
|
32
|
+
|
|
33
|
+
if pyproject.exists():
|
|
34
|
+
install_cmd = [str(python_bin), "-m", "pip", "install", "."]
|
|
35
|
+
logger.info("[%s] installing deps: %s", module_name, " ".join(install_cmd))
|
|
36
|
+
_run_checked(install_cmd, module_path, module_name, "install dependencies from pyproject.toml")
|
|
37
|
+
elif req.exists():
|
|
38
|
+
install_cmd = [str(python_bin), "-m", "pip", "install", "-r", str(req)]
|
|
39
|
+
logger.info("[%s] installing deps: %s", module_name, " ".join(install_cmd))
|
|
40
|
+
_run_checked(install_cmd, module_path, module_name, "install requirements")
|
|
41
|
+
else:
|
|
42
|
+
logger.info("[%s] reusing existing venv at %s", module_name, venv_path)
|
|
43
|
+
|
|
44
|
+
return python_bin
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _run_checked(cmd: list[str], module_path: Path, module_name: str, action: str) -> None:
|
|
48
|
+
try:
|
|
49
|
+
subprocess.run(
|
|
50
|
+
cmd,
|
|
51
|
+
check=True,
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
cwd=str(module_path),
|
|
55
|
+
)
|
|
56
|
+
except subprocess.CalledProcessError as exc:
|
|
57
|
+
stderr = exc.stderr.strip() if exc.stderr else str(exc)
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
f"Environment '{EnvironmentMode.PIP.value}' failed to {action} for module '{module_name}': {stderr}"
|
|
60
|
+
) from exc
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .constants import PYPROJECT_TOML, REQUIREMENTS_TXT
|
|
7
|
+
from .mode import EnvironmentMode
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("module_runner")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def resolve_mode(module_path: Path, requested: EnvironmentMode, module_name: str) -> EnvironmentMode:
|
|
13
|
+
"""Determine which environment mode should be used for the module."""
|
|
14
|
+
if requested is not EnvironmentMode.AUTO:
|
|
15
|
+
return requested
|
|
16
|
+
|
|
17
|
+
has_pyproject = (module_path / PYPROJECT_TOML).exists()
|
|
18
|
+
has_requirements = (module_path / REQUIREMENTS_TXT).exists()
|
|
19
|
+
uv_available = shutil.which("uv") is not None
|
|
20
|
+
pip_available = importlib.util.find_spec("venv") is not None
|
|
21
|
+
|
|
22
|
+
if has_pyproject:
|
|
23
|
+
if uv_available:
|
|
24
|
+
logger.info("[%s] strategy=uv (pyproject.toml detected, uv available)", module_name)
|
|
25
|
+
return EnvironmentMode.UV
|
|
26
|
+
|
|
27
|
+
if pip_available:
|
|
28
|
+
logger.info("[%s] strategy=pip (pyproject.toml detected, uv unavailable — fallback to venv+pip)", module_name)
|
|
29
|
+
return EnvironmentMode.PIP
|
|
30
|
+
|
|
31
|
+
detail = f"{PYPROJECT_TOML} detected but neither uv nor venv is available"
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
f"Environment setup failed for module '{module_name}': {detail}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if has_requirements:
|
|
37
|
+
if uv_available:
|
|
38
|
+
logger.info("[%s] strategy=uv (requirements.txt detected, uv available)", module_name)
|
|
39
|
+
return EnvironmentMode.UV
|
|
40
|
+
if pip_available:
|
|
41
|
+
logger.info("[%s] strategy=pip (requirements.txt detected, uv unavailable — fallback to venv+pip)", module_name)
|
|
42
|
+
return EnvironmentMode.PIP
|
|
43
|
+
|
|
44
|
+
detail = f"{REQUIREMENTS_TXT} detected but pip is unavailable"
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
f"Environment '{EnvironmentMode.PIP.value}' is unavailable for module '{module_name}': {detail}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
logger.info("[%s] strategy=system (no dependency file found, using current interpreter)", module_name)
|
|
50
|
+
return EnvironmentMode.SYSTEM
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def venv_python(venv_path: Path) -> Path:
|
|
6
|
+
"""Return the Python interpreter path inside a virtual environment."""
|
|
7
|
+
if sys.platform.startswith("win"):
|
|
8
|
+
return venv_path / "Scripts" / "python.exe"
|
|
9
|
+
return venv_path / "bin" / "python"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .constants import PYPROJECT_TOML, REQUIREMENTS_TXT, VENV_DIR
|
|
7
|
+
from .utils import venv_python
|
|
8
|
+
from .mode import EnvironmentMode
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("module_runner")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ensure_uv_environment(module_path: Path, module_name: str) -> Path:
|
|
14
|
+
"""Ensure uv is available and provision dependencies in the module directory."""
|
|
15
|
+
uv_bin = shutil.which("uv")
|
|
16
|
+
if uv_bin is None:
|
|
17
|
+
raise RuntimeError(
|
|
18
|
+
f"Environment '{EnvironmentMode.UV.value}' is unavailable for module '{module_name}': uv executable not found in PATH"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
requirements_path = module_path / REQUIREMENTS_TXT
|
|
22
|
+
pyproject_path = module_path / PYPROJECT_TOML
|
|
23
|
+
|
|
24
|
+
if requirements_path.exists() and not pyproject_path.exists():
|
|
25
|
+
venv_path = module_path / VENV_DIR
|
|
26
|
+
python_bin = venv_python(venv_path)
|
|
27
|
+
if not venv_path.exists():
|
|
28
|
+
create_cmd = [uv_bin, "venv"]
|
|
29
|
+
logger.info("[%s] creating venv: %s", module_name, " ".join(create_cmd))
|
|
30
|
+
_run_uv_checked(create_cmd, module_path, module_name, "create virtual environment")
|
|
31
|
+
else:
|
|
32
|
+
logger.info("[%s] reusing existing venv at %s", module_name, venv_path)
|
|
33
|
+
install_cmd = [uv_bin, "pip", "install", "-r", str(requirements_path)]
|
|
34
|
+
logger.info("[%s] installing deps: %s", module_name, " ".join(install_cmd))
|
|
35
|
+
_run_uv_checked(install_cmd, module_path, module_name, "install dependencies from requirements.txt")
|
|
36
|
+
return python_bin
|
|
37
|
+
|
|
38
|
+
logger.info("[%s] using uv run for pyproject.toml-based module", module_name)
|
|
39
|
+
return Path("uv")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _run_uv_checked(
|
|
43
|
+
cmd: list[str],
|
|
44
|
+
module_path: Path,
|
|
45
|
+
module_name: str,
|
|
46
|
+
action: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
try:
|
|
49
|
+
subprocess.run(
|
|
50
|
+
cmd,
|
|
51
|
+
check=True,
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
cwd=str(module_path),
|
|
55
|
+
)
|
|
56
|
+
except subprocess.CalledProcessError as exc:
|
|
57
|
+
stderr = exc.stderr.strip() if exc.stderr else str(exc)
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
f"Environment '{EnvironmentMode.UV.value}' failed to {action} for module '{module_name}': {stderr}"
|
|
60
|
+
) from exc
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RunnerExecutionError(Exception):
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
module: str,
|
|
8
|
+
exit_code: int,
|
|
9
|
+
stderr: str,
|
|
10
|
+
stdout: str = "",
|
|
11
|
+
cmd: Optional[list[str]] = None,
|
|
12
|
+
):
|
|
13
|
+
self.module = module
|
|
14
|
+
self.exit_code = exit_code
|
|
15
|
+
self.stderr = stderr
|
|
16
|
+
self.stdout = stdout
|
|
17
|
+
self.cmd = cmd
|
|
18
|
+
|
|
19
|
+
parts = [f"Module '{module}' failed with exit code {exit_code}"]
|
|
20
|
+
|
|
21
|
+
if cmd:
|
|
22
|
+
parts.append(f" command : {' '.join(cmd)}")
|
|
23
|
+
|
|
24
|
+
if stderr.strip():
|
|
25
|
+
parts.append(f" stderr : {stderr.strip()}")
|
|
26
|
+
|
|
27
|
+
if stdout.strip():
|
|
28
|
+
parts.append(f" stdout : {stdout.strip()}")
|
|
29
|
+
|
|
30
|
+
if not stderr.strip() and not stdout.strip():
|
|
31
|
+
parts.append(" (no output captured)")
|
|
32
|
+
|
|
33
|
+
super().__init__("\n".join(parts))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from subprocess import CompletedProcess
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .exceptions import RunnerExecutionError
|
|
9
|
+
from .environment import EnvironmentManager, EnvironmentMode
|
|
10
|
+
from .environment.constants import MAIN_PY
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("module_runner")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Runner:
|
|
16
|
+
|
|
17
|
+
def __init__(self, module_path: str | Path, environment: EnvironmentMode | str = EnvironmentMode.AUTO) -> None:
|
|
18
|
+
self.module_path = Path(module_path).resolve()
|
|
19
|
+
self.environment = environment
|
|
20
|
+
self.module_name = self.module_path.name
|
|
21
|
+
|
|
22
|
+
def run(
|
|
23
|
+
self,
|
|
24
|
+
payload: Optional[dict] = None,
|
|
25
|
+
) -> CompletedProcess[str]:
|
|
26
|
+
script_path = self.module_path / MAIN_PY
|
|
27
|
+
|
|
28
|
+
if not script_path.exists():
|
|
29
|
+
raise FileNotFoundError(f"Module '{self.module_name}' does not contain {MAIN_PY}")
|
|
30
|
+
|
|
31
|
+
env_manager = EnvironmentManager(self.module_path, self.environment)
|
|
32
|
+
executor = env_manager.ensure()
|
|
33
|
+
|
|
34
|
+
if str(executor) == "uv":
|
|
35
|
+
cmd = ["uv", "run", "--project", str(self.module_path), "python", str(script_path)]
|
|
36
|
+
else:
|
|
37
|
+
cmd = [str(executor), str(script_path)]
|
|
38
|
+
|
|
39
|
+
if payload is not None:
|
|
40
|
+
cmd.append(json.dumps(payload))
|
|
41
|
+
|
|
42
|
+
logger.info("[%s] executing: %s", self.module_name, " ".join(cmd))
|
|
43
|
+
|
|
44
|
+
process: CompletedProcess[str] = subprocess.run(
|
|
45
|
+
cmd,
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
cwd=str(self.module_path),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if process.returncode != 0:
|
|
52
|
+
raise RunnerExecutionError(
|
|
53
|
+
module=self.module_name,
|
|
54
|
+
exit_code=process.returncode,
|
|
55
|
+
stderr=process.stderr,
|
|
56
|
+
stdout=process.stdout,
|
|
57
|
+
cmd=cmd,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return process
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: module-runner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight helper that prepares Python environments and executes standalone modules.
|
|
5
|
+
Author: Leo Jaimesson
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
# Module Runner
|
|
12
|
+
|
|
13
|
+
Module Runner sits in the gap between ad-hoc scripts and heavyweight orchestration frameworks. It gives each module a predictable, isolated environment without asking you to bolt on extra infrastructure, schedulers, or workflow engines. The project stays intentionally lightweight and focuses on two responsibilities:
|
|
14
|
+
|
|
15
|
+
1. Detect the proper execution strategy (system Python, `venv`, or `uv`).
|
|
16
|
+
2. Launch the module with an optional JSON payload, returning the `subprocess.CompletedProcess` so you can decide how to consume `stdout`, `stderr`, or exit codes.
|
|
17
|
+
|
|
18
|
+
Any higher-level orchestration (pipelines, RPC, shared storage, etc.) is an application concern, keeping this library small and predictable.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- ⚙️ **Automatic environment discovery**: detects `pyproject.toml` or `requirements.txt` (→ `uv` when available, `venv` as fallback), or falls back to the current interpreter.
|
|
23
|
+
- 📦 **Per-module dependencies**: each module lives in its own folder with its own toolchain, avoiding cross-contamination.
|
|
24
|
+
- 🧱 **Opinionated boundary**: no implicit payload chaining—what a module prints, writes, or exposes is entirely up to you.
|
|
25
|
+
- 🪪 **Clear error surface**: `RunnerExecutionError` captures module failures while missing tooling raises descriptive `RuntimeError`s, keeping debugging straightforward.
|
|
26
|
+
- 🔍 **Built-in logging**: uses Python's standard `logging` module under the `module_runner` logger — enable `DEBUG` to see the resolved strategy, every setup command, and the final execution command.
|
|
27
|
+
|
|
28
|
+
## Logging
|
|
29
|
+
|
|
30
|
+
Module Runner emits `INFO`-level log records under the `module_runner` logger. To see them:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import logging
|
|
34
|
+
logging.getLogger("module_runner").setLevel(logging.INFO)
|
|
35
|
+
logging.basicConfig()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Example output:
|
|
39
|
+
```
|
|
40
|
+
[normalize] strategy=pip (requirements.txt detected, uv unavailable — fallback to venv+pip)
|
|
41
|
+
[normalize] creating venv: /usr/bin/python3 -m venv .venv
|
|
42
|
+
[normalize] installing deps: .venv/bin/python -m pip install -r requirements.txt
|
|
43
|
+
[normalize] executing: .venv/bin/python main.py {"text": " Hello World "}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Why This Exists
|
|
47
|
+
|
|
48
|
+
Module Runner is the missing middle layer between one-off helper scripts and enterprise orchestrators. It gives you just enough structure to keep automation tidy while remaining lightweight, infrastructure-free, and dependency-light.
|
|
49
|
+
|
|
50
|
+
## When to Use
|
|
51
|
+
|
|
52
|
+
Use Module Runner when you:
|
|
53
|
+
|
|
54
|
+
- Need simple local automation or scripting glue with a lightweight footprint
|
|
55
|
+
- Have dependency conflicts between scripts and want per-module isolation
|
|
56
|
+
- Want predictable, sequential execution you can reason about
|
|
57
|
+
- Prefer to stay container-free for lightweight tasks
|
|
58
|
+
- Do not need a workflow engine or task scheduler
|
|
59
|
+
|
|
60
|
+
## When Not to Use
|
|
61
|
+
|
|
62
|
+
Reach for other tooling if you require:
|
|
63
|
+
|
|
64
|
+
- Complex DAG dependencies or branching workflows
|
|
65
|
+
- Scheduling, cron-style orchestration, or SLAs
|
|
66
|
+
- Distributed workers or autoscaling fleets
|
|
67
|
+
- Monitoring dashboards, retries, or alerting UI
|
|
68
|
+
- Enterprise workflow/orchestration guarantees
|
|
69
|
+
|
|
70
|
+
## Philosophy
|
|
71
|
+
|
|
72
|
+
- Small, explicit, predictable, and lightweight
|
|
73
|
+
- Infrastructure-free: relies on the Python already on your machine
|
|
74
|
+
- Focused on one problem—launching modules in clean environments
|
|
75
|
+
|
|
76
|
+
It is not meant to be a workflow engine; it simply keeps isolated scripts manageable.
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# using pip
|
|
82
|
+
pip install module_runner
|
|
83
|
+
|
|
84
|
+
# using uv
|
|
85
|
+
uv add module_runner
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You only need Python 3.9+ and whichever tooling your modules request (e.g., `uv`, `venv`, system packages).
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from module_runner import Runner
|
|
95
|
+
|
|
96
|
+
runner = Runner(module_path="modules/normalize_text")
|
|
97
|
+
|
|
98
|
+
process = runner.run(
|
|
99
|
+
payload={"text": " Hello World "},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
print(process.stdout) # or json.loads(process.stdout)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Your module layout needs a `main.py` entry point. Any payload you pass is serialized as JSON and delivered as the last CLI argument. How you emit results (stdout, files, sockets) is entirely your call.
|
|
106
|
+
|
|
107
|
+
## Environment Selection
|
|
108
|
+
|
|
109
|
+
| Signal in module folder | Mode used | Requirement |
|
|
110
|
+
| ----------------------- | ------------- | ----------- |
|
|
111
|
+
| `pyproject.toml` | `uv run ...` | `uv` available in `PATH` |
|
|
112
|
+
| `pyproject.toml` (no `uv`) | `python -m venv` + `pip install .` | `venv` module available |
|
|
113
|
+
| `requirements.txt` | `uv venv` + `uv pip install -r requirements.txt` | `uv` available in `PATH` |
|
|
114
|
+
| `requirements.txt` (no `uv`) | `python -m venv` + `pip install -r requirements.txt` | `venv` module available |
|
|
115
|
+
| none of the above | current interpreter (`sys.executable`) | none |
|
|
116
|
+
|
|
117
|
+
- Auto-detection always checks whether the required tooling is installed. When `uv` is available it is preferred for both `pyproject.toml` and `requirements.txt` modules. If `uv` is not available, both `pyproject.toml` and `requirements.txt` modules fall back to `venv`/`pip`.
|
|
118
|
+
- If a module signals that it needs `uv` or `venv` but the corresponding tooling is missing, Module Runner raises a descriptive `RuntimeError` explaining what needs to be installed.
|
|
119
|
+
- Runtime failures propagate as `RunnerExecutionError`, exposing the module name, exit code, captured `stderr`, captured `stdout`, and the exact `cmd` list that was executed — making it straightforward to reproduce or log failures.
|
|
120
|
+
|
|
121
|
+
## Sandbox Playground
|
|
122
|
+
|
|
123
|
+
The [sandbox](sandbox/README.md) directory ships with two toy modules (`normalize` and `stats`) that showcase:
|
|
124
|
+
|
|
125
|
+
- How `requirements.txt` triggers a module-specific virtual environment.
|
|
126
|
+
- How `pyproject.toml` causes execution via `uv run`.
|
|
127
|
+
- How you can manually chain modules by parsing the `stdout` from the first run and feeding it into the next.
|
|
128
|
+
|
|
129
|
+
Run everything with:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python sandbox/run_examples.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use this folder as a template to create your own modules or to test different deployment scenarios.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/<seu-usuario>/module_runner.git
|
|
141
|
+
cd module_runner
|
|
142
|
+
python -m venv .venv && source .venv/bin/activate
|
|
143
|
+
pip install -e .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Feel free to open issues or pull requests with improvements. The scope intentionally stays small: reliable environment setup and process execution.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
Module Runner is released under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
module_runner/__init__.py
|
|
5
|
+
module_runner/environments.py
|
|
6
|
+
module_runner/exceptions.py
|
|
7
|
+
module_runner/runner.py
|
|
8
|
+
module_runner.egg-info/PKG-INFO
|
|
9
|
+
module_runner.egg-info/SOURCES.txt
|
|
10
|
+
module_runner.egg-info/dependency_links.txt
|
|
11
|
+
module_runner.egg-info/top_level.txt
|
|
12
|
+
module_runner/environment/__init__.py
|
|
13
|
+
module_runner/environment/constants.py
|
|
14
|
+
module_runner/environment/manager.py
|
|
15
|
+
module_runner/environment/mode.py
|
|
16
|
+
module_runner/environment/pip_env.py
|
|
17
|
+
module_runner/environment/resolver.py
|
|
18
|
+
module_runner/environment/utils.py
|
|
19
|
+
module_runner/environment/uv_env.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module_runner
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "module-runner"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Lightweight helper that prepares Python environments and executes standalone modules."
|
|
5
|
+
authors = [{name = "Leo Jaimesson"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
[tool.setuptools.packages.find]
|
|
11
|
+
include = ["module_runner*"]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["setuptools"]
|
|
15
|
+
build-backend = "setuptools.build_meta"
|