algomancy-cli 0.3.17__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.
- algomancy_cli/__init__.py +0 -0
- algomancy_cli/cli_configuration.py +67 -0
- algomancy_cli/cli_launcher.py +34 -0
- algomancy_cli/cli_shell.py +170 -0
- algomancy_cli/main.py +98 -0
- algomancy_cli-0.3.17.dist-info/METADATA +49 -0
- algomancy_cli-0.3.17.dist-info/RECORD +8 -0
- algomancy_cli-0.3.17.dist-info/WHEEL +4 -0
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from algomancy_scenario.core_configuration import CoreConfiguration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CliConfiguration(CoreConfiguration):
|
|
9
|
+
"""Configuration for the CLI application.
|
|
10
|
+
|
|
11
|
+
Extends the core configuration with CLI-specific options.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
# core parameters (see CoreConfiguration)
|
|
17
|
+
data_path: str = "data",
|
|
18
|
+
has_persistent_state: bool = False,
|
|
19
|
+
save_type: str | None = "json",
|
|
20
|
+
data_object_type: type | None = None,
|
|
21
|
+
etl_factory: Any | None = None,
|
|
22
|
+
kpi_templates: Dict[str, Any] | None = None,
|
|
23
|
+
algo_templates: Dict[str, Any] | None = None,
|
|
24
|
+
input_configs: list | None = None,
|
|
25
|
+
autocreate: bool | None = None,
|
|
26
|
+
default_algo: str | None = None,
|
|
27
|
+
default_algo_params_values: Dict[str, Any] | None = None,
|
|
28
|
+
autorun: bool | None = None,
|
|
29
|
+
title: str = "Algomancy CLI",
|
|
30
|
+
# CLI specific
|
|
31
|
+
verbose: bool = True,
|
|
32
|
+
**kwargs: Any,
|
|
33
|
+
) -> None:
|
|
34
|
+
super().__init__(
|
|
35
|
+
data_path=data_path,
|
|
36
|
+
has_persistent_state=has_persistent_state,
|
|
37
|
+
save_type=save_type,
|
|
38
|
+
data_object_type=data_object_type,
|
|
39
|
+
etl_factory=etl_factory,
|
|
40
|
+
kpi_templates=kpi_templates,
|
|
41
|
+
algo_templates=algo_templates,
|
|
42
|
+
input_configs=input_configs,
|
|
43
|
+
autocreate=autocreate,
|
|
44
|
+
default_algo=default_algo,
|
|
45
|
+
default_algo_params_values=default_algo_params_values,
|
|
46
|
+
autorun=autorun,
|
|
47
|
+
title=title,
|
|
48
|
+
**kwargs,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.verbose = verbose
|
|
52
|
+
self._validate_cli()
|
|
53
|
+
|
|
54
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
55
|
+
base = super().as_dict()
|
|
56
|
+
base.update(
|
|
57
|
+
{
|
|
58
|
+
"verbose": self.verbose,
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
return base
|
|
62
|
+
|
|
63
|
+
def _validate_cli(self) -> None:
|
|
64
|
+
if self.verbose is None:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
"Boolean configuration 'verbose' must be set to True or False, not None"
|
|
67
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Dict, Any, Union
|
|
2
|
+
|
|
3
|
+
from algomancy_scenario.scenariomanager import ScenarioManager
|
|
4
|
+
from algomancy_scenario.core_configuration import CoreConfiguration
|
|
5
|
+
|
|
6
|
+
from .cli_shell import CliShell
|
|
7
|
+
from .cli_configuration import CliConfiguration
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CliLauncher:
|
|
11
|
+
@staticmethod
|
|
12
|
+
def build(
|
|
13
|
+
cfg: Union[CliConfiguration, CoreConfiguration, Dict[str, Any]],
|
|
14
|
+
) -> CliShell:
|
|
15
|
+
"""Create a CLI shell from a CliConfiguration/CoreConfiguration or equivalent dict."""
|
|
16
|
+
if isinstance(cfg, dict):
|
|
17
|
+
cfg_obj = CliConfiguration(**cfg)
|
|
18
|
+
elif isinstance(cfg, CliConfiguration):
|
|
19
|
+
cfg_obj = cfg
|
|
20
|
+
elif isinstance(cfg, CoreConfiguration):
|
|
21
|
+
# allow passing a CoreConfiguration; wrap minimally
|
|
22
|
+
cfg_obj = CliConfiguration(**cfg.as_dict())
|
|
23
|
+
else:
|
|
24
|
+
raise TypeError(
|
|
25
|
+
"CliLauncher.build expects CliConfiguration, CoreConfiguration, or dict"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
sm = ScenarioManager.from_config(cfg_obj)
|
|
29
|
+
return CliShell(sm)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def run(shell: CliShell) -> None:
|
|
33
|
+
"""Run the interactive shell."""
|
|
34
|
+
shell.run()
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import shlex
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
from algomancy_scenario import ScenarioManager
|
|
6
|
+
from algomancy_utils.logger import MessageStatus
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CliShell:
|
|
10
|
+
"""
|
|
11
|
+
Minimal interactive shell to exercise backend functionality via ScenarioManager.
|
|
12
|
+
|
|
13
|
+
This is intended for backend development and manual testing without the GUI.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, scenario_manager: ScenarioManager):
|
|
17
|
+
self.sm = scenario_manager
|
|
18
|
+
self._commands: dict[str, Callable[[list[str]], None]] = {
|
|
19
|
+
"help": self._cmd_help,
|
|
20
|
+
"h": self._cmd_help,
|
|
21
|
+
"?": self._cmd_help,
|
|
22
|
+
"quit": self._cmd_quit,
|
|
23
|
+
"exit": self._cmd_quit,
|
|
24
|
+
"list-scenarios": self._cmd_list_scenarios,
|
|
25
|
+
"ls": self._cmd_list_scenarios,
|
|
26
|
+
"list-data": self._cmd_list_data,
|
|
27
|
+
"ld": self._cmd_list_data,
|
|
28
|
+
"load-data": self._cmd_load_data,
|
|
29
|
+
"etl-data": self._cmd_etl_data,
|
|
30
|
+
"create-scenario": self._cmd_create_scenario,
|
|
31
|
+
"run": self._cmd_run,
|
|
32
|
+
"status": self._cmd_status,
|
|
33
|
+
}
|
|
34
|
+
self._running = False
|
|
35
|
+
|
|
36
|
+
# ---------- Public API ----------
|
|
37
|
+
def run(self) -> None:
|
|
38
|
+
self._running = True
|
|
39
|
+
self.sm.log(
|
|
40
|
+
"Algomancy CLI Shell. Type 'help' for available commands.",
|
|
41
|
+
MessageStatus.SUCCESS,
|
|
42
|
+
)
|
|
43
|
+
while self._running:
|
|
44
|
+
try:
|
|
45
|
+
raw = input("algomancy> ").strip()
|
|
46
|
+
except (EOFError, KeyboardInterrupt):
|
|
47
|
+
print()
|
|
48
|
+
break
|
|
49
|
+
if not raw:
|
|
50
|
+
continue
|
|
51
|
+
parts = shlex.split(raw)
|
|
52
|
+
cmd, args = parts[0], parts[1:]
|
|
53
|
+
handler = self._commands.get(cmd)
|
|
54
|
+
if handler is None:
|
|
55
|
+
self.sm.log(
|
|
56
|
+
f"Unknown command: {cmd}. Type 'help' for a list of commands.",
|
|
57
|
+
MessageStatus.ERROR,
|
|
58
|
+
)
|
|
59
|
+
continue
|
|
60
|
+
try:
|
|
61
|
+
handler(args)
|
|
62
|
+
except Exception as ex: # keep shell alive during development
|
|
63
|
+
self.sm.log(f"Error: {ex}", MessageStatus.ERROR)
|
|
64
|
+
|
|
65
|
+
# ---------- Command handlers ----------
|
|
66
|
+
def _cmd_help(self, _: list[str]) -> None:
|
|
67
|
+
print(
|
|
68
|
+
"\nAvailable commands:\n"
|
|
69
|
+
" help | h | ? Show this help.\n"
|
|
70
|
+
" quit | exit Exit the shell.\n"
|
|
71
|
+
" list-data | ld List available datasets.\n"
|
|
72
|
+
" load-data <name> Load example data into dataset <name>.\n"
|
|
73
|
+
" etl-data <name> Run ETL to create dataset <name>.\n"
|
|
74
|
+
" list-scenarios | ls List scenarios.\n"
|
|
75
|
+
" create-scenario <tag> <dataset_key> <algo> [json_params]\n"
|
|
76
|
+
" Create a scenario. Params as JSON object.\n"
|
|
77
|
+
" run <scenario_id_or_tag> Run a scenario (waits until complete).\n"
|
|
78
|
+
" status Show processing status.\n"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _cmd_quit(self, _: list[str]) -> None:
|
|
82
|
+
self._running = False
|
|
83
|
+
|
|
84
|
+
def _cmd_list_data(self, _: list[str]) -> None:
|
|
85
|
+
keys = self.sm.get_data_keys()
|
|
86
|
+
if not keys:
|
|
87
|
+
print("No datasets available.")
|
|
88
|
+
return
|
|
89
|
+
print("Datasets:")
|
|
90
|
+
for k in keys:
|
|
91
|
+
print(f" - {k}")
|
|
92
|
+
|
|
93
|
+
def _cmd_load_data(self, args: list[str]) -> None:
|
|
94
|
+
if len(args) < 1:
|
|
95
|
+
self.sm.log("Usage: load-data <dataset_name>", MessageStatus.WARNING)
|
|
96
|
+
return
|
|
97
|
+
name = args[0]
|
|
98
|
+
self.sm.debug_load_data(name)
|
|
99
|
+
self.sm.log(
|
|
100
|
+
f"Loaded example data into dataset '{name}'.", MessageStatus.SUCCESS
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _cmd_etl_data(self, args: list[str]) -> None:
|
|
104
|
+
if len(args) < 1:
|
|
105
|
+
self.sm.log("Usage: etl-data <dataset_name>", MessageStatus.WARNING)
|
|
106
|
+
return
|
|
107
|
+
name = args[0]
|
|
108
|
+
self.sm.debug_etl_data(name)
|
|
109
|
+
self.sm.log(
|
|
110
|
+
f"ETL completed, dataset '{name}' available.", MessageStatus.SUCCESS
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _cmd_list_scenarios(self, _: list[str]) -> None:
|
|
114
|
+
scenarios = self.sm.list_scenarios()
|
|
115
|
+
if not scenarios:
|
|
116
|
+
print("No scenarios defined.")
|
|
117
|
+
return
|
|
118
|
+
print("Scenarios:")
|
|
119
|
+
for s in scenarios:
|
|
120
|
+
print(
|
|
121
|
+
f" - id={s.id} tag={s.tag} algo={s.algo_name} dataset={s.dataset_key} status={getattr(s, 'status', 'n/a')}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def _cmd_create_scenario(self, args: list[str]) -> None:
|
|
125
|
+
if len(args) < 3:
|
|
126
|
+
self.sm.log(
|
|
127
|
+
"Usage: create-scenario <tag> <dataset_key> <algo_name> [json_params]",
|
|
128
|
+
MessageStatus.WARNING,
|
|
129
|
+
)
|
|
130
|
+
return
|
|
131
|
+
tag, dataset_key, algo_name = args[0], args[1], args[2]
|
|
132
|
+
params: dict[str, Any] | None = None
|
|
133
|
+
if len(args) >= 4:
|
|
134
|
+
try:
|
|
135
|
+
params = json.loads(args[3])
|
|
136
|
+
except json.JSONDecodeError:
|
|
137
|
+
self.sm.log("Invalid JSON for parameters.", MessageStatus.ERROR)
|
|
138
|
+
return
|
|
139
|
+
s = self.sm.create_scenario(
|
|
140
|
+
tag=tag, dataset_key=dataset_key, algo_name=algo_name, algo_params=params
|
|
141
|
+
)
|
|
142
|
+
self.sm.log(f"Created scenario id={s.id} tag={s.tag}", MessageStatus.SUCCESS)
|
|
143
|
+
|
|
144
|
+
def _resolve_scenario(self, key: str):
|
|
145
|
+
s = self.sm.get_by_id(key)
|
|
146
|
+
if s is None:
|
|
147
|
+
s = self.sm.get_by_tag(key)
|
|
148
|
+
return s
|
|
149
|
+
|
|
150
|
+
def _cmd_run(self, args: list[str]) -> None:
|
|
151
|
+
if len(args) < 1:
|
|
152
|
+
self.sm.log("Usage: run <scenario_id_or_tag>", MessageStatus.WARNING)
|
|
153
|
+
return
|
|
154
|
+
identifier = args[0]
|
|
155
|
+
s = self._resolve_scenario(identifier)
|
|
156
|
+
if s is None:
|
|
157
|
+
self.sm.log(f"Scenario '{identifier}' not found.", MessageStatus.ERROR)
|
|
158
|
+
return
|
|
159
|
+
self.sm.process_scenario_async(s)
|
|
160
|
+
self.sm.wait_for_processing()
|
|
161
|
+
self.sm.log(f"Scenario '{s.tag}' completed.", MessageStatus.SUCCESS)
|
|
162
|
+
|
|
163
|
+
def _cmd_status(self, _: list[str]) -> None:
|
|
164
|
+
processing = self.sm.currently_processing
|
|
165
|
+
if processing:
|
|
166
|
+
print(
|
|
167
|
+
f" - id={processing.id} tag={processing.tag} status={getattr(processing, 'status', 'processing')}"
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
print("No scenarios processing.")
|
algomancy_cli/main.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _ensure_dev_path():
|
|
9
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
10
|
+
project_root = os.path.abspath(os.path.join(here, "..", "..", "..", ".."))
|
|
11
|
+
if project_root not in sys.path:
|
|
12
|
+
sys.path.insert(0, project_root)
|
|
13
|
+
try:
|
|
14
|
+
from algomancy_cli.cli_configuration import CliConfiguration # noqa: F401
|
|
15
|
+
from algomancy_cli.cli_launcher import CliLauncher # noqa: F401
|
|
16
|
+
|
|
17
|
+
return
|
|
18
|
+
except Exception:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_ensure_dev_path()
|
|
23
|
+
|
|
24
|
+
from algomancy_cli.cli_configuration import CliConfiguration # type: ignore # noqa: E402
|
|
25
|
+
from algomancy_cli.cli_launcher import CliLauncher # type: ignore # noqa: E402
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _parse_args():
|
|
29
|
+
parser = argparse.ArgumentParser(description="Algomancy CLI shell")
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--config-callback",
|
|
32
|
+
type=str,
|
|
33
|
+
default=None,
|
|
34
|
+
help=(
|
|
35
|
+
"Callback to construct CliConfiguration, in the form 'module:function'. "
|
|
36
|
+
"The function must return a CliConfiguration instance."
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--example",
|
|
41
|
+
action="store_true",
|
|
42
|
+
help="Use the example configuration bundled in this repository (for development).",
|
|
43
|
+
)
|
|
44
|
+
return parser.parse_args()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _load_config_from_callback(spec: str) -> CliConfiguration:
|
|
48
|
+
if ":" not in spec:
|
|
49
|
+
raise ValueError("--config-callback must be in 'module:function' form")
|
|
50
|
+
module_name, func_name = spec.split(":", 1)
|
|
51
|
+
module = importlib.import_module(module_name)
|
|
52
|
+
func: Callable[[], CliConfiguration] = getattr(module, func_name)
|
|
53
|
+
cfg = func()
|
|
54
|
+
if not isinstance(cfg, CliConfiguration):
|
|
55
|
+
raise TypeError("Config callback did not return CliConfiguration")
|
|
56
|
+
return cfg
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _build_example_config() -> CliConfiguration:
|
|
60
|
+
# These imports rely on this repo's example package being available in the workspace
|
|
61
|
+
from example.data_handling.input_configs import example_input_configs
|
|
62
|
+
from example.data_handling.factories import ExampleETLFactory
|
|
63
|
+
from example.templates import kpi_templates, algorithm_templates
|
|
64
|
+
|
|
65
|
+
from algomancy_data import DataSource
|
|
66
|
+
|
|
67
|
+
return CliConfiguration(
|
|
68
|
+
data_path="example/data",
|
|
69
|
+
has_persistent_state=True,
|
|
70
|
+
etl_factory=ExampleETLFactory,
|
|
71
|
+
kpi_templates=kpi_templates,
|
|
72
|
+
algo_templates=algorithm_templates,
|
|
73
|
+
input_configs=example_input_configs,
|
|
74
|
+
data_object_type=DataSource,
|
|
75
|
+
autocreate=True,
|
|
76
|
+
default_algo="Slow",
|
|
77
|
+
default_algo_params_values={"duration": 1},
|
|
78
|
+
autorun=True,
|
|
79
|
+
title="Algomancy CLI (Example)",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main():
|
|
84
|
+
args = _parse_args()
|
|
85
|
+
if args.config_callback:
|
|
86
|
+
cfg = _load_config_from_callback(args.config_callback)
|
|
87
|
+
elif args.example:
|
|
88
|
+
cfg = _build_example_config()
|
|
89
|
+
else:
|
|
90
|
+
print("Either pass --config-callback module:function or use --example")
|
|
91
|
+
sys.exit(2)
|
|
92
|
+
|
|
93
|
+
shell = CliLauncher.build(cfg)
|
|
94
|
+
CliLauncher.run(shell)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: algomancy-cli
|
|
3
|
+
Version: 0.3.17
|
|
4
|
+
Summary: CLI shell for Algomancy to exercise backend functionality without the GUI.
|
|
5
|
+
Requires-Dist: algomancy-utils
|
|
6
|
+
Requires-Dist: algomancy-scenario
|
|
7
|
+
Requires-Dist: algomancy-data
|
|
8
|
+
Requires-Python: >=3.14
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
Algomancy CLI
|
|
12
|
+
|
|
13
|
+
Interactive terminal shell to exercise Algomancy backend functionality without the GUI. Useful for rapid development of ETL, algorithms, and scenarios.
|
|
14
|
+
|
|
15
|
+
Install / Run
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
uv run algomancy-cli --example
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or point to your own configuration factory:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
uv run algomancy-cli --config-callback myproject.config:make_config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Where `make_config` returns an `AppConfiguration` instance.
|
|
28
|
+
|
|
29
|
+
Commands
|
|
30
|
+
|
|
31
|
+
- `help` — show help
|
|
32
|
+
- `list-data` / `ld` — list datasets
|
|
33
|
+
- `load-data <name>` — load example data into dataset `<name>`
|
|
34
|
+
- `etl-data <name>` — run ETL to create dataset `<name>`
|
|
35
|
+
- `list-scenarios` / `ls` — list scenarios
|
|
36
|
+
- `create-scenario <tag> <dataset_key> <algo_name> [json_params]` — create scenario
|
|
37
|
+
- `run <scenario_id_or_tag>` — run scenario and wait for completion
|
|
38
|
+
- `status` — show processing status
|
|
39
|
+
- `quit` / `exit` — exit shell
|
|
40
|
+
|
|
41
|
+
Parameters for `create-scenario` can be provided as a JSON object, e.g.:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
create-scenario test1 "Master data" Fast "{\"duration\": 0.5}"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
How it works
|
|
48
|
+
|
|
49
|
+
The CLI wraps `ScenarioManager` created via `AppConfiguration` just like the GUI launcher. See `src/algomancy/cli_launcher.py` for details.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
algomancy_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
algomancy_cli/cli_configuration.py,sha256=2skq8tJyJHKTU6HLRzmbK7ULRuSMLWzWtj-tknGgFtg,2088
|
|
3
|
+
algomancy_cli/cli_launcher.py,sha256=ztU9PRcuetpD45ioGgfvmrLd7yEzB3y5gicCWio6970,1156
|
|
4
|
+
algomancy_cli/cli_shell.py,sha256=_KJnf4p9SWbs9B0-zOBxu7n5LGMWO3INcw1ojAypXL0,6372
|
|
5
|
+
algomancy_cli/main.py,sha256=2VlfHENrYfyaxeYlkAJGJKygwYzWMom3Y1Mpl3Eb0ng,3069
|
|
6
|
+
algomancy_cli-0.3.17.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
7
|
+
algomancy_cli-0.3.17.dist-info/METADATA,sha256=U-WNZOh1GnPiWp12KWl-01ImXD6KfdOxJ2WwBdxMZJs,1477
|
|
8
|
+
algomancy_cli-0.3.17.dist-info/RECORD,,
|