gemstone-engine 1.2.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.
- gemstone_engine-1.2.0/LICENSE +12 -0
- gemstone_engine-1.2.0/PKG-INFO +109 -0
- gemstone_engine-1.2.0/README.md +101 -0
- gemstone_engine-1.2.0/README_PyPI.md +97 -0
- gemstone_engine-1.2.0/pyproject.toml +24 -0
- gemstone_engine-1.2.0/setup.cfg +4 -0
- gemstone_engine-1.2.0/setup.py +15 -0
- gemstone_engine-1.2.0/src/gemstone/__init__.py +0 -0
- gemstone_engine-1.2.0/src/gemstone/cli.py +149 -0
- gemstone_engine-1.2.0/src/gemstone/dir.py +22 -0
- gemstone_engine-1.2.0/src/gemstone/generic_helpers.py +502 -0
- gemstone_engine-1.2.0/src/gemstone/iofile.py +173 -0
- gemstone_engine-1.2.0/src/gemstone/jobid.py +47 -0
- gemstone_engine-1.2.0/src/gemstone/params.py +267 -0
- gemstone_engine-1.2.0/src/gemstone/paths.py +208 -0
- gemstone_engine-1.2.0/src/gemstone/pipeliner.py +189 -0
- gemstone_engine-1.2.0/src/gemstone/specs.py +425 -0
- gemstone_engine-1.2.0/src/gemstone/stage.py +85 -0
- gemstone_engine-1.2.0/src/gemstone/stageflows.py +371 -0
- gemstone_engine-1.2.0/src/gemstone/standards.py +95 -0
- gemstone_engine-1.2.0/src/gemstone/testing.py +136 -0
- gemstone_engine-1.2.0/src/gemstone_engine.egg-info/PKG-INFO +109 -0
- gemstone_engine-1.2.0/src/gemstone_engine.egg-info/SOURCES.txt +24 -0
- gemstone_engine-1.2.0/src/gemstone_engine.egg-info/dependency_links.txt +1 -0
- gemstone_engine-1.2.0/src/gemstone_engine.egg-info/entry_points.txt +2 -0
- gemstone_engine-1.2.0/src/gemstone_engine.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Creative Commons Attribution–NoDerivatives 4.0 International (CC BY-ND 4.0)
|
|
2
|
+
Copyright © 2025 Your Name
|
|
3
|
+
|
|
4
|
+
This work is licensed under the Creative Commons Attribution–NoDerivatives 4.0 International License.
|
|
5
|
+
You may share — copy and redistribute the material in any medium or format — under the following terms:
|
|
6
|
+
|
|
7
|
+
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
|
|
8
|
+
|
|
9
|
+
No Derivatives — If you remix, transform, or build upon the material, you may not distribute the modified material.
|
|
10
|
+
|
|
11
|
+
To view a copy of this license, visit:
|
|
12
|
+
https://creativecommons.org/licenses/by-nd/4.0/
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gemstone_engine
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: GEMSTONe: Generic Engine for Modular Software Task Orchestration and Negotiation
|
|
5
|
+
Author-email: Milo Fryling <milo.fryling@duke.edu>
|
|
6
|
+
License: CC BY-ND 4.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# GEMSTONe: Generic Engine for Modular Software Task Orchestration and Negotiation
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
GEMSTONe is an engine that runs customizable modular workflows. We use the term "module" in the usual sense — a standalone process that takes inputs and produces outputs.
|
|
18
|
+
When modules are linked together in a workflow, GEMSTONe refers to each one as a "stage"; otherwise terms "module" and "stage" can be used interchangeably.
|
|
19
|
+
Each stage is a standalone processing step: it reads input files, does its job, and writes outputs. A workflow links these stages together in sequence. GEMSTONe takes care of running them in order, passing data along the way.
|
|
20
|
+
|
|
21
|
+
Your role is to make each stage GEMSTONe-ready so the engine can:
|
|
22
|
+
|
|
23
|
+
- Start it with the right parameters
|
|
24
|
+
- Handle all file paths and connections between stages automatically
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install gemstone-engine
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Design Goals
|
|
33
|
+
|
|
34
|
+
- **No proprietary language.** Simple and obvious human-readable syntax for all configuration files (`specs.txt`, `iofiles.txt`, `stageflows.txt`, etc.)
|
|
35
|
+
|
|
36
|
+
- **Boilerplate code eliminated.** Parameter validation, path management, file compatibility, and chaining are handled by the engine.
|
|
37
|
+
|
|
38
|
+
- **Zero coupling.** Module code and engine code are 100% independent — changes to one never require changes to the other.
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
To plug a stage into GEMSTONe, just include these three files:
|
|
43
|
+
|
|
44
|
+
- `specs.txt`: lists user input parameters and any defaults or constraints
|
|
45
|
+
- `iofiles.txt`: defines the stage’s inputs and outputs in table format
|
|
46
|
+
- `run.py`: contains `def run(params, paths):`, which launches your code
|
|
47
|
+
- Your module’s own implementation lives in a `runtime/` subdirectory inside the stage folder.
|
|
48
|
+
|
|
49
|
+
To connect stages into a workflow, add:
|
|
50
|
+
|
|
51
|
+
- `stageflows.txt`: shows how outputs from one stage become inputs to the next
|
|
52
|
+
- `standards.txt`: lists the types of files your workflow uses
|
|
53
|
+
|
|
54
|
+
That’s it. With these files in place, GEMSTONe handles path management and input validation automatically — no need to write custom code for filenames, directories, or parameter parsing.
|
|
55
|
+
|
|
56
|
+
## Example Usage
|
|
57
|
+
|
|
58
|
+
**Launching a workflow**
|
|
59
|
+
|
|
60
|
+
A GEMSTONe workflow is started from the command line through the standard entry point:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from gemstone.cli import main
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This accepts:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
<param file>
|
|
73
|
+
[<jobid override>]
|
|
74
|
+
[--testmode]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Inside a stage (`run.py`)**
|
|
78
|
+
|
|
79
|
+
Each stage defines:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
def run(params: Params, paths: Paths):
|
|
83
|
+
param_file = paths.get_parameter_file_path()
|
|
84
|
+
write_param_file(param_file, params)
|
|
85
|
+
args = make_args(params, param_file, paths)
|
|
86
|
+
|
|
87
|
+
subprocess.run(
|
|
88
|
+
args,
|
|
89
|
+
cwd=paths.stage,
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
check=True
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The engine supplies both `params` and `paths`; your code focuses only on launching the underlying executable.
|
|
97
|
+
|
|
98
|
+
## Project Status
|
|
99
|
+
|
|
100
|
+
GEMSTONe is under active development and already in use for production research pipelines. Interfaces shown here are stable, but minor refinements are expected as the documentation and tooling expand.
|
|
101
|
+
|
|
102
|
+
## Documentation
|
|
103
|
+
|
|
104
|
+
Full integration documentation will be released with the public source repository.
|
|
105
|
+
Until then, concise usage notes and guidance are available—please reach out through the project’s PyPI page or institutional contact channels if you need assistance integrating a module or workflow.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
Creative Commons BY‑ND 4.0
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
## Name
|
|
2
|
+
DukeSim 3 (provisional name)
|
|
3
|
+
|
|
4
|
+
## Purpose (objectives & intended audience/users)
|
|
5
|
+
This provides a generic, extensible framework for running very loosely coupled software components as a pipeline.
|
|
6
|
+
It provides an engine for modular orchestration, and an app library containing templates and a sample application
|
|
7
|
+
|
|
8
|
+
## OS Platform, Language, dependencies
|
|
9
|
+
The software runs on Linux and is written in Python.
|
|
10
|
+
|
|
11
|
+
## How to build, install and/or publish
|
|
12
|
+
Use directly from a clone of the repo. There is no build or publishing procedure.
|
|
13
|
+
To reset or switch the active pipeline, rerun `use_app_stages.py` with a new app name.
|
|
14
|
+
|
|
15
|
+
## User Documentation
|
|
16
|
+
|
|
17
|
+
**For full instructions and detailed information, see:**
|
|
18
|
+
|
|
19
|
+
### `src/USERGUIDE.md`
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Quick Overview (For Reference Only):
|
|
24
|
+
|
|
25
|
+
#### Summary:
|
|
26
|
+
- `src/app_library`: Holds the catalog of pipelines.
|
|
27
|
+
- `src/stages`: Holds the working copy of the active app.
|
|
28
|
+
- `src/params`: Holds parameter files.
|
|
29
|
+
- `stages.py` and `dirs.py` (at app level): Define a pipeline.
|
|
30
|
+
- `specs.txt` and `run.py` (in each component): Define the parameters and launch interface.
|
|
31
|
+
- `src/engine`: *Must not* be modified by the user.
|
|
32
|
+
|
|
33
|
+
#### Common Commands:
|
|
34
|
+
- **Select an app to activate:**
|
|
35
|
+
```bash
|
|
36
|
+
python3 src/use_app_stages.py <app_name> # App name is the directory in app_library
|
|
37
|
+
```
|
|
38
|
+
- **Run the numerous unit tests in `src/tests`:**
|
|
39
|
+
```bash
|
|
40
|
+
src/do_tests.sh
|
|
41
|
+
```
|
|
42
|
+
- **Run a pipeline from the command line:**
|
|
43
|
+
```bash
|
|
44
|
+
python3 run.py params/<param_file_name>
|
|
45
|
+
# Optional: override auto-generated Job ID
|
|
46
|
+
python3 run.py params/<param_file_name> <jobid_override>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Activate app and run pipeline in one step (convenience wrapper):**
|
|
50
|
+
```bash
|
|
51
|
+
./run_app.sh <app_name> <param_file> [jobid_override] [--testmode]
|
|
52
|
+
```
|
|
53
|
+
## App directory structure
|
|
54
|
+
App Repos Will:
|
|
55
|
+
|
|
56
|
+
- Be Git repos named like G-Recon, G-VIT, etc.
|
|
57
|
+
- Be self-contained, not installed via pip, but invoked from repo root.
|
|
58
|
+
- Use the name stages/ for the directory containing stage code.
|
|
59
|
+
- Place stageflows.txt inside the stages/ directory.
|
|
60
|
+
- Place params_example.txt and run_app.py at the repo root.
|
|
61
|
+
- Place job outputs in output/ (created by the engine), located at the repo root.
|
|
62
|
+
|
|
63
|
+
## Packaging the engine and building apps
|
|
64
|
+
App Dev with GEMSTONe Clone on Same Server
|
|
65
|
+
- git clone git@gitlab.com:gemstone-world/GEMSTONe.git
|
|
66
|
+
- cd GEMSTONe
|
|
67
|
+
- pip install -e .
|
|
68
|
+
|
|
69
|
+
- cd ~/repos
|
|
70
|
+
- git clone git@gitlab.com:gemstone-world/G-Recon.git
|
|
71
|
+
- cd G-Recon
|
|
72
|
+
- python run_app.py params_example.txt
|
|
73
|
+
|
|
74
|
+
To update engine:
|
|
75
|
+
cd ~/repos/GEMSTONe
|
|
76
|
+
git pull
|
|
77
|
+
|
|
78
|
+
App Dev in the Field (pip install only)
|
|
79
|
+
- pip install gemstone_engine
|
|
80
|
+
|
|
81
|
+
- cd ~/repos
|
|
82
|
+
- git clone git@gitlab.com:gemstone-world/G-Recon.git
|
|
83
|
+
- cd G-Recon
|
|
84
|
+
- python run_app.py params_example.txt
|
|
85
|
+
|
|
86
|
+
To update engine:
|
|
87
|
+
pip install --upgrade gemstone_engine
|
|
88
|
+
|
|
89
|
+
Engine Publishing (per release)
|
|
90
|
+
- cd GEMSTONe
|
|
91
|
+
- git pull
|
|
92
|
+
- (bump version in pyproject.toml)
|
|
93
|
+
- python -m build
|
|
94
|
+
- twine upload dist/*
|
|
95
|
+
|
|
96
|
+
Then users can do:
|
|
97
|
+
pip install gemstone_engine
|
|
98
|
+
pip install --upgrade gemstone_engine
|
|
99
|
+
|
|
100
|
+
## License details
|
|
101
|
+
No licensing has been considered yet.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# GEMSTONe: Generic Engine for Modular Software Task Orchestration and Negotiation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
GEMSTONe is an engine that runs customizable modular workflows. We use the term "module" in the usual sense — a standalone process that takes inputs and produces outputs.
|
|
6
|
+
When modules are linked together in a workflow, GEMSTONe refers to each one as a "stage"; otherwise terms "module" and "stage" can be used interchangeably.
|
|
7
|
+
Each stage is a standalone processing step: it reads input files, does its job, and writes outputs. A workflow links these stages together in sequence. GEMSTONe takes care of running them in order, passing data along the way.
|
|
8
|
+
|
|
9
|
+
Your role is to make each stage GEMSTONe-ready so the engine can:
|
|
10
|
+
|
|
11
|
+
- Start it with the right parameters
|
|
12
|
+
- Handle all file paths and connections between stages automatically
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install gemstone-engine
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Design Goals
|
|
21
|
+
|
|
22
|
+
- **No proprietary language.** Simple and obvious human-readable syntax for all configuration files (`specs.txt`, `iofiles.txt`, `stageflows.txt`, etc.)
|
|
23
|
+
|
|
24
|
+
- **Boilerplate code eliminated.** Parameter validation, path management, file compatibility, and chaining are handled by the engine.
|
|
25
|
+
|
|
26
|
+
- **Zero coupling.** Module code and engine code are 100% independent — changes to one never require changes to the other.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
To plug a stage into GEMSTONe, just include these three files:
|
|
31
|
+
|
|
32
|
+
- `specs.txt`: lists user input parameters and any defaults or constraints
|
|
33
|
+
- `iofiles.txt`: defines the stage’s inputs and outputs in table format
|
|
34
|
+
- `run.py`: contains `def run(params, paths):`, which launches your code
|
|
35
|
+
- Your module’s own implementation lives in a `runtime/` subdirectory inside the stage folder.
|
|
36
|
+
|
|
37
|
+
To connect stages into a workflow, add:
|
|
38
|
+
|
|
39
|
+
- `stageflows.txt`: shows how outputs from one stage become inputs to the next
|
|
40
|
+
- `standards.txt`: lists the types of files your workflow uses
|
|
41
|
+
|
|
42
|
+
That’s it. With these files in place, GEMSTONe handles path management and input validation automatically — no need to write custom code for filenames, directories, or parameter parsing.
|
|
43
|
+
|
|
44
|
+
## Example Usage
|
|
45
|
+
|
|
46
|
+
**Launching a workflow**
|
|
47
|
+
|
|
48
|
+
A GEMSTONe workflow is started from the command line through the standard entry point:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from gemstone.cli import main
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This accepts:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
<param file>
|
|
61
|
+
[<jobid override>]
|
|
62
|
+
[--testmode]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Inside a stage (`run.py`)**
|
|
66
|
+
|
|
67
|
+
Each stage defines:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
def run(params: Params, paths: Paths):
|
|
71
|
+
param_file = paths.get_parameter_file_path()
|
|
72
|
+
write_param_file(param_file, params)
|
|
73
|
+
args = make_args(params, param_file, paths)
|
|
74
|
+
|
|
75
|
+
subprocess.run(
|
|
76
|
+
args,
|
|
77
|
+
cwd=paths.stage,
|
|
78
|
+
capture_output=True,
|
|
79
|
+
text=True,
|
|
80
|
+
check=True
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The engine supplies both `params` and `paths`; your code focuses only on launching the underlying executable.
|
|
85
|
+
|
|
86
|
+
## Project Status
|
|
87
|
+
|
|
88
|
+
GEMSTONe is under active development and already in use for production research pipelines. Interfaces shown here are stable, but minor refinements are expected as the documentation and tooling expand.
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
Full integration documentation will be released with the public source repository.
|
|
93
|
+
Until then, concise usage notes and guidance are available—please reach out through the project’s PyPI page or institutional contact channels if you need assistance integrating a module or workflow.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
Creative Commons BY‑ND 4.0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gemstone_engine" # distribution name (pip install gemstone_engine)
|
|
7
|
+
version = "1.2.0" # bump on each release
|
|
8
|
+
description = "GEMSTONe: Generic Engine for Modular Software Task Orchestration and Negotiation"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Milo Fryling", email="milo.fryling@duke.edu" }
|
|
11
|
+
]
|
|
12
|
+
readme = "README_PyPI.md"
|
|
13
|
+
license = { text = "CC BY-ND 4.0" }
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
classifiers = [ "Programming Language :: Python :: 3" ]
|
|
16
|
+
|
|
17
|
+
# what gets installed as the importable package
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
include = ["gemstone"]
|
|
20
|
+
where = ["src"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
gemstone = "gemstone.cli:main"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# setup.py
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
setup(
|
|
5
|
+
name="gemstone_engine",
|
|
6
|
+
version="1.0.0",
|
|
7
|
+
packages=find_packages(where="src"),
|
|
8
|
+
package_dir={"": "src"},
|
|
9
|
+
install_requires=[],
|
|
10
|
+
entry_points={
|
|
11
|
+
"console_scripts": [
|
|
12
|
+
"gemstone = gemstone.cli:main",
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
import subprocess
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from importlib.metadata import version as pkg_version, PackageNotFoundError
|
|
8
|
+
from gemstone.pipeliner import run_pipeline
|
|
9
|
+
import gemstone.dir as Dir
|
|
10
|
+
|
|
11
|
+
class ExitCode:
|
|
12
|
+
"""Standardized exit codes for pipeline errors."""
|
|
13
|
+
MISSING_FILE = 99
|
|
14
|
+
CONFIG_ERROR = 2
|
|
15
|
+
PERMISSION_ERROR = 3
|
|
16
|
+
KEY_ERROR = 4
|
|
17
|
+
RUNTIME_ERROR = 1
|
|
18
|
+
UNKNOWN_ERROR = 100
|
|
19
|
+
|
|
20
|
+
def get_version() -> str:
|
|
21
|
+
"""Returns GEMSTONe version from package metadata (installed) or git (dev)."""
|
|
22
|
+
try:
|
|
23
|
+
return pkg_version("gemstone_engine") # Use actual package name
|
|
24
|
+
except PackageNotFoundError:
|
|
25
|
+
try:
|
|
26
|
+
return subprocess.check_output(
|
|
27
|
+
["git", "describe", "--tags", "--dirty", "--always"],
|
|
28
|
+
stderr=subprocess.DEVNULL,
|
|
29
|
+
).decode().strip()
|
|
30
|
+
except Exception:
|
|
31
|
+
return "unknown"
|
|
32
|
+
|
|
33
|
+
def display_version(terse:bool = False):
|
|
34
|
+
"""Displays the pipeline version (from git describe)."""
|
|
35
|
+
version = get_version()
|
|
36
|
+
|
|
37
|
+
if terse:
|
|
38
|
+
print(f" GEMSTONe {version}")
|
|
39
|
+
else:
|
|
40
|
+
print('===========================')
|
|
41
|
+
print(f" GEMSTONe {version}")
|
|
42
|
+
print('===========================')
|
|
43
|
+
|
|
44
|
+
def show_error(msg, exit_code):
|
|
45
|
+
"""Prints and logs an error message, then exits."""
|
|
46
|
+
print(msg)
|
|
47
|
+
logging.error(msg)
|
|
48
|
+
sys.exit(exit_code)
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
"""Entry point to start the pipeline."""
|
|
52
|
+
parser = argparse.ArgumentParser(description="Run a GEMSTONe pipeline.")
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"params",
|
|
55
|
+
type=str,
|
|
56
|
+
nargs="?", # Make this argument optional
|
|
57
|
+
help="Path to the param file."
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"jobid_override",
|
|
61
|
+
type=str,
|
|
62
|
+
nargs="?", # Optional positional
|
|
63
|
+
help="Optional: override the auto-generated job ID, e.g. to restart a job."
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--test_mode",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Optional: run in test mode (create outputs without executing real code)."
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--version",
|
|
72
|
+
action="store_true",
|
|
73
|
+
help="Display GEMSTONe version and exit.")
|
|
74
|
+
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
if args.version:
|
|
77
|
+
display_version(True)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
display_version(False)
|
|
81
|
+
setup_logging()
|
|
82
|
+
|
|
83
|
+
if args.test_mode:
|
|
84
|
+
logging.warning("TEST MODE ENABLED — no real execution will occur.")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
Dir.set_root(os.getcwd())
|
|
88
|
+
run_pipeline(args.params, args.jobid_override, test_mode=args.test_mode)
|
|
89
|
+
|
|
90
|
+
except FileNotFoundError as e:
|
|
91
|
+
show_error(f"Error: {e}", ExitCode.MISSING_FILE)
|
|
92
|
+
|
|
93
|
+
except ValueError as e:
|
|
94
|
+
show_error(f"Configuration error: {e}", ExitCode.CONFIG_ERROR)
|
|
95
|
+
|
|
96
|
+
except PermissionError as e:
|
|
97
|
+
show_error(f"Permission error: {e}", ExitCode.PERMISSION_ERROR)
|
|
98
|
+
|
|
99
|
+
except RuntimeError as e:
|
|
100
|
+
show_error(f"Runtime error: {e}", ExitCode.RUNTIME_ERROR)
|
|
101
|
+
|
|
102
|
+
except KeyError as e:
|
|
103
|
+
show_error(f"Param key error: {e}", ExitCode.KEY_ERROR)
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logging.exception("Unexpected error:")
|
|
107
|
+
show_error("An unexpected error occurred. Check the log for details.", ExitCode.UNKNOWN_ERROR)
|
|
108
|
+
|
|
109
|
+
class DotMillisecondsFormatter(logging.Formatter):
|
|
110
|
+
def formatTime(self, record, datefmt=None):
|
|
111
|
+
t = datetime.fromtimestamp(record.created)
|
|
112
|
+
return t.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # Keep milliseconds only
|
|
113
|
+
|
|
114
|
+
def setup_logging():
|
|
115
|
+
"""Set up logging with consistent format and millisecond timestamps."""
|
|
116
|
+
log_file = "GEMSTONe.log"
|
|
117
|
+
|
|
118
|
+
root_logger = logging.getLogger()
|
|
119
|
+
root_logger.setLevel(logging.INFO)
|
|
120
|
+
|
|
121
|
+
# Remove any existing handlers
|
|
122
|
+
for handler in root_logger.handlers[:]:
|
|
123
|
+
root_logger.removeHandler(handler)
|
|
124
|
+
|
|
125
|
+
formatter = DotMillisecondsFormatter(
|
|
126
|
+
"%(asctime)s - %(levelname)s - %(message)s"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
file_handler = logging.FileHandler(log_file)
|
|
130
|
+
file_handler.setLevel(logging.INFO)
|
|
131
|
+
file_handler.setFormatter(formatter)
|
|
132
|
+
|
|
133
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
134
|
+
console_handler.setLevel(logging.INFO)
|
|
135
|
+
console_handler.setFormatter(formatter)
|
|
136
|
+
|
|
137
|
+
root_logger.addHandler(file_handler)
|
|
138
|
+
root_logger.addHandler(console_handler)
|
|
139
|
+
|
|
140
|
+
# Ensure submodules propagate to root
|
|
141
|
+
logging.getLogger('subprocess').propagate = True
|
|
142
|
+
logging.getLogger('gemstone').propagate = True
|
|
143
|
+
logging.getLogger('stages').propagate = True
|
|
144
|
+
|
|
145
|
+
logging.info(f"Logging to file: {log_file}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
main()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# dir.py - absolute path directory layout constants in deployed environment
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
root = "."
|
|
5
|
+
stages = "."
|
|
6
|
+
output = "."
|
|
7
|
+
|
|
8
|
+
def set_root(root_path):
|
|
9
|
+
global root, stages, output
|
|
10
|
+
root = os.path.abspath(root_path)
|
|
11
|
+
stages = os.path.join(root, "stages")
|
|
12
|
+
output = os.path.join(root, "output")
|
|
13
|
+
|
|
14
|
+
def validate(subdirs=None):
|
|
15
|
+
if root in (".", None):
|
|
16
|
+
raise RuntimeError("Dir.set_root() must be called before validate()")
|
|
17
|
+
|
|
18
|
+
check = subdirs or ["stages", "output"]
|
|
19
|
+
for d in check:
|
|
20
|
+
full = os.path.join(root, d)
|
|
21
|
+
if not os.path.isdir(full):
|
|
22
|
+
raise RuntimeError(f"Missing required directory: {full}")
|