crazy-workers 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.
Files changed (34) hide show
  1. crazy_workers-0.1.0/LICENSE +19 -0
  2. crazy_workers-0.1.0/PKG-INFO +247 -0
  3. crazy_workers-0.1.0/README.md +212 -0
  4. crazy_workers-0.1.0/crazy_workers/__init__.py +5 -0
  5. crazy_workers-0.1.0/crazy_workers/_bootstrap.py +33 -0
  6. crazy_workers-0.1.0/crazy_workers/cli/__init__.py +4 -0
  7. crazy_workers-0.1.0/crazy_workers/cli/commands/__init__.py +8 -0
  8. crazy_workers-0.1.0/crazy_workers/cli/commands/lister.py +57 -0
  9. crazy_workers-0.1.0/crazy_workers/cli/commands/params.py +37 -0
  10. crazy_workers-0.1.0/crazy_workers/cli/commands/restorer.py +14 -0
  11. crazy_workers-0.1.0/crazy_workers/cli/commands/starter.py +36 -0
  12. crazy_workers-0.1.0/crazy_workers/cli/commands/stopper.py +30 -0
  13. crazy_workers-0.1.0/crazy_workers/cli/discovery.py +93 -0
  14. crazy_workers-0.1.0/crazy_workers/cli/main.py +85 -0
  15. crazy_workers-0.1.0/crazy_workers/cli/ui.py +9 -0
  16. crazy_workers-0.1.0/crazy_workers/core/__init__.py +4 -0
  17. crazy_workers-0.1.0/crazy_workers/core/engine.py +90 -0
  18. crazy_workers-0.1.0/crazy_workers/core/manager/__init__.py +89 -0
  19. crazy_workers-0.1.0/crazy_workers/core/manager/lister.py +67 -0
  20. crazy_workers-0.1.0/crazy_workers/core/manager/recoverer.py +25 -0
  21. crazy_workers-0.1.0/crazy_workers/core/manager/starter.py +137 -0
  22. crazy_workers-0.1.0/crazy_workers/core/manager/stopper.py +58 -0
  23. crazy_workers-0.1.0/crazy_workers/core/recovery.py +68 -0
  24. crazy_workers-0.1.0/crazy_workers/database/__init__.py +5 -0
  25. crazy_workers-0.1.0/crazy_workers/database/schema.py +42 -0
  26. crazy_workers-0.1.0/crazy_workers/database/storage.py +56 -0
  27. crazy_workers-0.1.0/crazy_workers.egg-info/PKG-INFO +247 -0
  28. crazy_workers-0.1.0/crazy_workers.egg-info/SOURCES.txt +32 -0
  29. crazy_workers-0.1.0/crazy_workers.egg-info/dependency_links.txt +1 -0
  30. crazy_workers-0.1.0/crazy_workers.egg-info/entry_points.txt +2 -0
  31. crazy_workers-0.1.0/crazy_workers.egg-info/requires.txt +9 -0
  32. crazy_workers-0.1.0/crazy_workers.egg-info/top_level.txt +1 -0
  33. crazy_workers-0.1.0/pyproject.toml +83 -0
  34. crazy_workers-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2024 GioVanni Colasanto
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: crazy-workers
3
+ Version: 0.1.0
4
+ Summary: A Python library for managing background worker processes with persistent state, automatic recovery, and a CLI.
5
+ Author: GioVanni Colasanto
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Vanni-broUser/crazy-workers
8
+ Project-URL: Bug Tracker, https://github.com/Vanni-broUser/crazy-workers/issues
9
+ Project-URL: Source, https://github.com/Vanni-broUser/crazy-workers
10
+ Keywords: workers,background,processes,process-manager,task-runner,cli,psutil
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: sqlalchemy>=2.0.0
27
+ Requires-Dist: psutil>=5.9.0
28
+ Requires-Dist: rich>=13.0.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: ruff; extra == "dev"
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: coverage; extra == "dev"
33
+ Requires-Dist: flask; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Crazy Workers
37
+
38
+ A Python library for managing background worker processes with persistent state, automatic crash recovery, and a built-in CLI.
39
+
40
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
42
+
43
+ ## Features
44
+
45
+ - **Persistent State** — SQLite database tracks worker status, PIDs, and parameters across restarts.
46
+ - **Process Management** — Start, stop, and monitor background Python scripts as independent OS processes.
47
+ - **Automatic Recovery** — Detects crashed workers and restarts them on application boot.
48
+ - **Child Process Control** — On stop, terminates unmanaged subprocesses while preserving independently-managed nested workers.
49
+ - **CLI Interface** — Manage workers from the terminal with interactive prompts and auto-discovery (see [CLI.md](CLI.md)).
50
+ - **Security** — Built-in protection against path traversal in worker type and key names.
51
+ - **Observability** — Per-worker file logging; all service files (DB, lock, logs) live in a `.service/` folder inside your workers directory.
52
+ - **Zombie Protection** — Distinguishes active processes from zombies using `psutil`.
53
+ - **Gunicorn-safe** — File-based lock prevents concurrent recovery runs across multiple workers.
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install crazy-workers
59
+ ```
60
+
61
+ Or from source:
62
+
63
+ ```bash
64
+ git clone https://github.com/Vanni-broUser/crazy-workers
65
+ cd crazy-workers
66
+ pip install .
67
+ ```
68
+
69
+ ## Quick Start
70
+
71
+ ### 1. Create a worker script
72
+
73
+ ```python
74
+ # workers/my_worker.py
75
+ import json, sys, time
76
+
77
+ params = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
78
+ duration = params.get('duration', 60)
79
+
80
+ for _ in range(duration):
81
+ time.sleep(1)
82
+ ```
83
+
84
+ ### 2. Manage it from Python
85
+
86
+ ```python
87
+ from crazy_workers import WorkerManager
88
+
89
+ manager = WorkerManager('workers')
90
+
91
+ # Start
92
+ success, result = manager.start_worker(
93
+ 'my_worker',
94
+ worker_key='job_1',
95
+ parameters={'duration': 30},
96
+ )
97
+ print(result['pid']) # OS process ID
98
+ print(result['status']) # 'RUNNING'
99
+
100
+ # List
101
+ for w in manager.list_workers():
102
+ print(w['worker_key'], w['status'])
103
+
104
+ # Stop
105
+ manager.stop_worker('job_1')
106
+
107
+ # Recover crashed workers (call on app startup)
108
+ restarted = manager.recover_workers()
109
+
110
+ manager.dispose() # releases DB connection; does NOT kill workers
111
+ ```
112
+
113
+ ### 3. Or from the CLI
114
+
115
+ ```bash
116
+ crazy-workers list
117
+ crazy-workers start my_worker --key job_1 --params '{"duration": 30}'
118
+ crazy-workers stop job_1
119
+ crazy-workers restore
120
+ ```
121
+
122
+ See [CLI.md](CLI.md) for full CLI documentation.
123
+
124
+ ## API Reference
125
+
126
+ ### `WorkerManager(workers_dir, create_dir=True)`
127
+
128
+ | Parameter | Type | Default | Description |
129
+ |-----------|------|---------|-------------|
130
+ | `workers_dir` | `str` | `'workers'` | Directory containing worker `.py` scripts |
131
+ | `create_dir` | `bool` | `True` | Create `workers_dir` and `.service/` if they don't exist |
132
+
133
+ ### `start_worker(worker_type, worker_key=None, parameters=None, env=None)`
134
+
135
+ | Parameter | Type | Default | Description |
136
+ |-----------|------|---------|-------------|
137
+ | `worker_type` | `str` | — | Filename (without `.py`) of the worker script |
138
+ | `worker_key` | `str` | `worker_type` | Unique identifier; allows multiple instances of the same type |
139
+ | `parameters` | `dict` | `{}` | JSON-serializable dict passed as `sys.argv[1]` to the worker |
140
+ | `env` | `dict` | `None` | Extra environment variables injected into the worker process |
141
+
142
+ Returns `(bool, dict | str)` — `(True, worker_dict)` on success, `(False, error_message)` on failure.
143
+
144
+ ### `stop_worker(worker_key)`
145
+
146
+ Gracefully terminates the worker (SIGTERM → SIGKILL after timeout). Returns `(bool, str)`.
147
+
148
+ ### `list_workers()`
149
+
150
+ Returns a list of worker dicts including RUNNING, STOPPED, CRASHED, and NEVER_STARTED (filesystem-discovered) workers.
151
+
152
+ ### `recover_workers()`
153
+
154
+ Restarts any worker whose DB status is RUNNING but whose process is dead. Uses a file lock to prevent concurrent recovery. Returns a list of restarted keys.
155
+
156
+ ### `dispose()`
157
+
158
+ Closes the database connection and clears internal process references. Does **not** kill background workers — they continue running independently.
159
+
160
+ ## Worker Script Contract
161
+
162
+ A worker receives its parameters as a JSON string in `sys.argv[1]`:
163
+
164
+ ```python
165
+ import json, sys
166
+
167
+ params = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
168
+ # ... do work ...
169
+ ```
170
+
171
+ ## Project Structure
172
+
173
+ ```
174
+ crazy_workers/ # Library package
175
+ core/ # WorkerManager, process engine, recovery lock
176
+ cli/ # CLI entry point, commands, discovery
177
+ database/ # SQLAlchemy schema and SQLite storage
178
+ example_app/ # Flask demo application
179
+ app.py
180
+ workers/ # Example worker scripts
181
+ tests/
182
+ core/ # Unit tests for core modules
183
+ cli/ # Unit tests for CLI modules
184
+ database/ # Unit tests for storage layer
185
+ integration/ # Full-stack integration tests (real processes)
186
+ app/ # Tests for the example Flask app
187
+ ```
188
+
189
+ ## Flask Integration
190
+
191
+ ```python
192
+ from crazy_workers import WorkerManager
193
+
194
+ def create_app():
195
+ app = Flask(__name__)
196
+ manager = WorkerManager('workers')
197
+
198
+ @app.route('/workers/start', methods=['POST'])
199
+ def start():
200
+ data = request.json
201
+ success, result = manager.start_worker(
202
+ data['worker_type'],
203
+ worker_key=data.get('worker_key'),
204
+ parameters=data.get('parameters', {}),
205
+ )
206
+ return (jsonify(result), 200) if success else (jsonify({'error': result}), 400)
207
+
208
+ manager.recover_workers() # restart any crashed workers on boot
209
+ return app
210
+ ```
211
+
212
+ See `example_app/app.py` for a complete example.
213
+
214
+ ## Gunicorn / Multi-Process Servers
215
+
216
+ When using a pre-fork server like Gunicorn:
217
+
218
+ - **Recovery is atomic** — a file lock (`.service/workers.db.recovery.lock`) ensures `recover_workers()` runs once even when multiple workers boot simultaneously.
219
+ - **Workers outlive their parent** — if a Gunicorn worker is recycled, background processes keep running. The next recovery cycle re-attaches or restarts them.
220
+
221
+ ## Development
222
+
223
+ ### Setup
224
+
225
+ ```bash
226
+ git clone https://github.com/Vanni-broUser/crazy-workers
227
+ cd crazy-workers
228
+ pip install -e .[dev]
229
+ ```
230
+
231
+ ### Commands
232
+
233
+ ```bash
234
+ # Lint and format
235
+ ruff check . --fix && ruff format .
236
+
237
+ # Run tests
238
+ pytest
239
+
240
+ # Run tests with coverage
241
+ coverage run -m pytest && coverage report
242
+ ```
243
+
244
+ ### Standards
245
+
246
+ See [AI.md](AI.md) for the full coding and testing standards used in this project.
247
+
@@ -0,0 +1,212 @@
1
+ # Crazy Workers
2
+
3
+ A Python library for managing background worker processes with persistent state, automatic crash recovery, and a built-in CLI.
4
+
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ ## Features
9
+
10
+ - **Persistent State** — SQLite database tracks worker status, PIDs, and parameters across restarts.
11
+ - **Process Management** — Start, stop, and monitor background Python scripts as independent OS processes.
12
+ - **Automatic Recovery** — Detects crashed workers and restarts them on application boot.
13
+ - **Child Process Control** — On stop, terminates unmanaged subprocesses while preserving independently-managed nested workers.
14
+ - **CLI Interface** — Manage workers from the terminal with interactive prompts and auto-discovery (see [CLI.md](CLI.md)).
15
+ - **Security** — Built-in protection against path traversal in worker type and key names.
16
+ - **Observability** — Per-worker file logging; all service files (DB, lock, logs) live in a `.service/` folder inside your workers directory.
17
+ - **Zombie Protection** — Distinguishes active processes from zombies using `psutil`.
18
+ - **Gunicorn-safe** — File-based lock prevents concurrent recovery runs across multiple workers.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install crazy-workers
24
+ ```
25
+
26
+ Or from source:
27
+
28
+ ```bash
29
+ git clone https://github.com/Vanni-broUser/crazy-workers
30
+ cd crazy-workers
31
+ pip install .
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### 1. Create a worker script
37
+
38
+ ```python
39
+ # workers/my_worker.py
40
+ import json, sys, time
41
+
42
+ params = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
43
+ duration = params.get('duration', 60)
44
+
45
+ for _ in range(duration):
46
+ time.sleep(1)
47
+ ```
48
+
49
+ ### 2. Manage it from Python
50
+
51
+ ```python
52
+ from crazy_workers import WorkerManager
53
+
54
+ manager = WorkerManager('workers')
55
+
56
+ # Start
57
+ success, result = manager.start_worker(
58
+ 'my_worker',
59
+ worker_key='job_1',
60
+ parameters={'duration': 30},
61
+ )
62
+ print(result['pid']) # OS process ID
63
+ print(result['status']) # 'RUNNING'
64
+
65
+ # List
66
+ for w in manager.list_workers():
67
+ print(w['worker_key'], w['status'])
68
+
69
+ # Stop
70
+ manager.stop_worker('job_1')
71
+
72
+ # Recover crashed workers (call on app startup)
73
+ restarted = manager.recover_workers()
74
+
75
+ manager.dispose() # releases DB connection; does NOT kill workers
76
+ ```
77
+
78
+ ### 3. Or from the CLI
79
+
80
+ ```bash
81
+ crazy-workers list
82
+ crazy-workers start my_worker --key job_1 --params '{"duration": 30}'
83
+ crazy-workers stop job_1
84
+ crazy-workers restore
85
+ ```
86
+
87
+ See [CLI.md](CLI.md) for full CLI documentation.
88
+
89
+ ## API Reference
90
+
91
+ ### `WorkerManager(workers_dir, create_dir=True)`
92
+
93
+ | Parameter | Type | Default | Description |
94
+ |-----------|------|---------|-------------|
95
+ | `workers_dir` | `str` | `'workers'` | Directory containing worker `.py` scripts |
96
+ | `create_dir` | `bool` | `True` | Create `workers_dir` and `.service/` if they don't exist |
97
+
98
+ ### `start_worker(worker_type, worker_key=None, parameters=None, env=None)`
99
+
100
+ | Parameter | Type | Default | Description |
101
+ |-----------|------|---------|-------------|
102
+ | `worker_type` | `str` | — | Filename (without `.py`) of the worker script |
103
+ | `worker_key` | `str` | `worker_type` | Unique identifier; allows multiple instances of the same type |
104
+ | `parameters` | `dict` | `{}` | JSON-serializable dict passed as `sys.argv[1]` to the worker |
105
+ | `env` | `dict` | `None` | Extra environment variables injected into the worker process |
106
+
107
+ Returns `(bool, dict | str)` — `(True, worker_dict)` on success, `(False, error_message)` on failure.
108
+
109
+ ### `stop_worker(worker_key)`
110
+
111
+ Gracefully terminates the worker (SIGTERM → SIGKILL after timeout). Returns `(bool, str)`.
112
+
113
+ ### `list_workers()`
114
+
115
+ Returns a list of worker dicts including RUNNING, STOPPED, CRASHED, and NEVER_STARTED (filesystem-discovered) workers.
116
+
117
+ ### `recover_workers()`
118
+
119
+ Restarts any worker whose DB status is RUNNING but whose process is dead. Uses a file lock to prevent concurrent recovery. Returns a list of restarted keys.
120
+
121
+ ### `dispose()`
122
+
123
+ Closes the database connection and clears internal process references. Does **not** kill background workers — they continue running independently.
124
+
125
+ ## Worker Script Contract
126
+
127
+ A worker receives its parameters as a JSON string in `sys.argv[1]`:
128
+
129
+ ```python
130
+ import json, sys
131
+
132
+ params = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
133
+ # ... do work ...
134
+ ```
135
+
136
+ ## Project Structure
137
+
138
+ ```
139
+ crazy_workers/ # Library package
140
+ core/ # WorkerManager, process engine, recovery lock
141
+ cli/ # CLI entry point, commands, discovery
142
+ database/ # SQLAlchemy schema and SQLite storage
143
+ example_app/ # Flask demo application
144
+ app.py
145
+ workers/ # Example worker scripts
146
+ tests/
147
+ core/ # Unit tests for core modules
148
+ cli/ # Unit tests for CLI modules
149
+ database/ # Unit tests for storage layer
150
+ integration/ # Full-stack integration tests (real processes)
151
+ app/ # Tests for the example Flask app
152
+ ```
153
+
154
+ ## Flask Integration
155
+
156
+ ```python
157
+ from crazy_workers import WorkerManager
158
+
159
+ def create_app():
160
+ app = Flask(__name__)
161
+ manager = WorkerManager('workers')
162
+
163
+ @app.route('/workers/start', methods=['POST'])
164
+ def start():
165
+ data = request.json
166
+ success, result = manager.start_worker(
167
+ data['worker_type'],
168
+ worker_key=data.get('worker_key'),
169
+ parameters=data.get('parameters', {}),
170
+ )
171
+ return (jsonify(result), 200) if success else (jsonify({'error': result}), 400)
172
+
173
+ manager.recover_workers() # restart any crashed workers on boot
174
+ return app
175
+ ```
176
+
177
+ See `example_app/app.py` for a complete example.
178
+
179
+ ## Gunicorn / Multi-Process Servers
180
+
181
+ When using a pre-fork server like Gunicorn:
182
+
183
+ - **Recovery is atomic** — a file lock (`.service/workers.db.recovery.lock`) ensures `recover_workers()` runs once even when multiple workers boot simultaneously.
184
+ - **Workers outlive their parent** — if a Gunicorn worker is recycled, background processes keep running. The next recovery cycle re-attaches or restarts them.
185
+
186
+ ## Development
187
+
188
+ ### Setup
189
+
190
+ ```bash
191
+ git clone https://github.com/Vanni-broUser/crazy-workers
192
+ cd crazy-workers
193
+ pip install -e .[dev]
194
+ ```
195
+
196
+ ### Commands
197
+
198
+ ```bash
199
+ # Lint and format
200
+ ruff check . --fix && ruff format .
201
+
202
+ # Run tests
203
+ pytest
204
+
205
+ # Run tests with coverage
206
+ coverage run -m pytest && coverage report
207
+ ```
208
+
209
+ ### Standards
210
+
211
+ See [AI.md](AI.md) for the full coding and testing standards used in this project.
212
+
@@ -0,0 +1,5 @@
1
+ from .core.manager import WorkerManager
2
+ from .database.schema import WorkerStatus
3
+
4
+
5
+ __all__ = ['WorkerManager', 'WorkerStatus']
@@ -0,0 +1,33 @@
1
+ """
2
+ Thin launcher invoked by WorkerManager for every worker subprocess.
3
+ Configures logging once so individual worker scripts don't have to.
4
+
5
+ Invocation (managed internally by WorkerManager):
6
+ python -m crazy_workers._bootstrap <worker_path> <json_params>
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ import runpy
12
+ import sys
13
+
14
+
15
+ def main():
16
+ logging.basicConfig(
17
+ level=logging.INFO,
18
+ format='%(asctime)s - %(levelname)s - %(message)s',
19
+ stream=sys.stderr,
20
+ force=True,
21
+ )
22
+
23
+ # Restore sys.argv so the worker sees [worker_path, json_params]
24
+ sys.argv = sys.argv[1:]
25
+
26
+ worker_path = sys.argv[0]
27
+ sys.path.insert(0, os.path.dirname(os.path.abspath(worker_path)))
28
+
29
+ runpy.run_path(worker_path, run_name='__main__')
30
+
31
+
32
+ if __name__ == '__main__':
33
+ main()
@@ -0,0 +1,4 @@
1
+ from .main import main
2
+
3
+
4
+ __all__ = ['main']
@@ -0,0 +1,8 @@
1
+ from .lister import list_workers
2
+ from .params import show_params
3
+ from .restorer import restore_workers
4
+ from .starter import start_worker
5
+ from .stopper import stop_worker
6
+
7
+
8
+ __all__ = ['list_workers', 'show_params', 'start_worker', 'stop_worker', 'restore_workers']
@@ -0,0 +1,57 @@
1
+ import json
2
+ from datetime import datetime
3
+ from rich.table import Table
4
+
5
+ from ..ui import console
6
+
7
+
8
+ def list_workers(manager):
9
+ workers = manager.list_workers()
10
+ if not workers:
11
+ console().print('[yellow]No workers found in database.[/yellow]')
12
+ return []
13
+ else:
14
+ table = Table(
15
+ title='[bold cyan]Active & Registered Workers[/bold cyan]', border_style='cyan', header_style='bold magenta'
16
+ )
17
+ table.add_column('#', justify='right', style='dim')
18
+ table.add_column('Key', style='bold')
19
+ table.add_column('Type')
20
+ table.add_column('Status', justify='center')
21
+ table.add_column('PID', justify='right', style='green')
22
+ table.add_column('Last Action', justify='center')
23
+ table.add_column('Params', overflow='ellipsis')
24
+
25
+ for i, w in enumerate(workers, 1):
26
+ status = w['status']
27
+ status_style = 'green' if status == 'RUNNING' else 'yellow'
28
+ if status in ['CRASHED', 'FAILED']:
29
+ status_style = 'bold red'
30
+ elif status == 'STOPPED':
31
+ status_style = 'dim'
32
+ elif status == 'NEVER_STARTED':
33
+ status_style = 'cyan'
34
+
35
+ last_action = '-'
36
+ if status == 'RUNNING' and w.get('last_started_at'):
37
+ dt = datetime.fromisoformat(w['last_started_at'])
38
+ last_action = f'[green]Started {dt.strftime("%H:%M:%S")}[/green]'
39
+ elif w.get('last_stopped_at'):
40
+ dt = datetime.fromisoformat(w['last_stopped_at'])
41
+ last_action = f'[dim]Stopped {dt.strftime("%H:%M:%S")}[/dim]'
42
+
43
+ params_str = json.dumps(w['parameters']) if w['parameters'] else '-'
44
+ if len(params_str) > 30:
45
+ params_str = params_str[:27] + '...'
46
+
47
+ table.add_row(
48
+ str(i),
49
+ w['worker_key'] or '-',
50
+ w['worker_type'],
51
+ f'[{status_style}]{status}[/{status_style}]',
52
+ str(w['pid']) if w['pid'] else '-',
53
+ last_action,
54
+ params_str,
55
+ )
56
+ console().print(table)
57
+ return workers
@@ -0,0 +1,37 @@
1
+ import json
2
+ from rich.prompt import IntPrompt
3
+
4
+ from ..ui import console, err_console
5
+
6
+
7
+ def show_params(manager, worker_key):
8
+
9
+ workers = manager.list_workers()
10
+ if not workers:
11
+ console().print('[yellow]No workers found.[/yellow]')
12
+ return False
13
+
14
+ if not worker_key:
15
+ # Interactive mode
16
+ active_workers = [w for w in workers if w['worker_key'] is not None]
17
+
18
+ if not active_workers:
19
+ console().print('[yellow]No registered workers to show parameters for.[/yellow]')
20
+ return False
21
+
22
+ console().print('\n[bold cyan]Select a worker to show parameters:[/bold cyan]')
23
+ for i, w in enumerate(active_workers, 1):
24
+ status_style = 'green' if w['status'] == 'RUNNING' else 'dim'
25
+ console().print(f' [bold]{i})[/bold] {w["worker_key"]} [{status_style}]({w["status"]})[/{status_style}]')
26
+
27
+ choice = IntPrompt.ask('Enter the number', choices=[str(i) for i in range(1, len(active_workers) + 1)])
28
+ selected_worker = active_workers[choice - 1]
29
+ else:
30
+ selected_worker = next((w for w in workers if w['worker_key'] == worker_key), None)
31
+ if not selected_worker:
32
+ err_console().print(f'[bold red]Error:[/bold red] Worker {worker_key} not found')
33
+ return False
34
+
35
+ console().print(f'\n[bold cyan]Parameters for worker:[/bold cyan] {selected_worker["worker_key"]}')
36
+ console().print_json(json.dumps(selected_worker['parameters']))
37
+ return True
@@ -0,0 +1,14 @@
1
+ from ..ui import console
2
+
3
+
4
+ def restore_workers(manager):
5
+ restarted = manager.recover_workers()
6
+
7
+ if restarted:
8
+ console().print(f'[bold green]Successfully restored {len(restarted)} workers:[/bold green]')
9
+ for key in restarted:
10
+ console().print(f' - {key}')
11
+ return True
12
+ else:
13
+ console().print('[yellow]No workers needed restoration.[/yellow]')
14
+ return True
@@ -0,0 +1,36 @@
1
+ import os
2
+ from rich.prompt import IntPrompt
3
+
4
+ from ..ui import console, err_console
5
+
6
+
7
+ def start_worker(manager, worker_type, worker_key=None, parameters=None):
8
+
9
+ if not worker_type:
10
+ # Interactive mode: list .py files in workers_dir
11
+ try:
12
+ files = [f[:-3] for f in os.listdir(manager.workers_dir) if f.endswith('.py')]
13
+ except Exception as e:
14
+ err_console().print(f'[bold red]Error reading workers directory:[/bold red] {e}')
15
+ return False
16
+
17
+ if not files:
18
+ console().print(f'[yellow]No worker scripts found in {manager.workers_dir}[/yellow]')
19
+ return False
20
+
21
+ console().print('\n[bold cyan]Select a worker type to start:[/bold cyan]')
22
+ for i, f in enumerate(files, 1):
23
+ console().print(f' [bold]{i})[/bold] {f}')
24
+
25
+ choice = IntPrompt.ask('Enter the number', choices=[str(i) for i in range(1, len(files) + 1)])
26
+ worker_type = files[choice - 1]
27
+
28
+ success, result = manager.start_worker(worker_type, worker_key=worker_key, parameters=parameters)
29
+ if success:
30
+ console().print('[bold green]Success:[/bold green] Worker started')
31
+ console().print(f' [bold]Key:[/bold] {result["worker_key"]}')
32
+ console().print(f' [bold]PID:[/bold] {result["pid"]}')
33
+ else:
34
+ err_console().print(f'[bold red]Error:[/bold red] {result}')
35
+ return False
36
+ return True