steve-cli 0.1.0__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.
steve_cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Steve CLI package
|
steve_cli/cli.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Steve CLI - A simple tool to run jobs from jobs.yaml with proper environment setup.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
steve <job-name> Run a job with its environment variables
|
|
7
|
+
steve ls List all available jobs
|
|
8
|
+
steve help Show this help message
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Any, Optional
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class JobsConfig:
|
|
22
|
+
"""Handle parsing and querying of jobs.yaml configuration."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, jobs_file: Optional[Path] = None):
|
|
25
|
+
self.jobs_file = jobs_file or Path.cwd() / "jobs.yaml"
|
|
26
|
+
self.jobs: List[Dict[str, Any]] = []
|
|
27
|
+
self._load_jobs()
|
|
28
|
+
|
|
29
|
+
def _load_jobs(self) -> None:
|
|
30
|
+
"""Load and parse the jobs.yaml file."""
|
|
31
|
+
try:
|
|
32
|
+
if not self.jobs_file.exists():
|
|
33
|
+
click.echo(f"❌ Error: jobs.yaml not found at {self.jobs_file}", err=True)
|
|
34
|
+
click.echo("Make sure you're in a directory with a jobs.yaml file", err=True)
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
with open(self.jobs_file, 'r') as f:
|
|
38
|
+
data = yaml.safe_load(f)
|
|
39
|
+
|
|
40
|
+
if not data or 'jobs' not in data:
|
|
41
|
+
click.echo(f"❌ Error: Invalid jobs.yaml format. Expected 'jobs' key at root", err=True)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
self.jobs = data['jobs']
|
|
45
|
+
|
|
46
|
+
except yaml.YAMLError as e:
|
|
47
|
+
click.echo(f"❌ Error parsing jobs.yaml: {e}", err=True)
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
click.echo(f"❌ Error reading jobs.yaml: {e}", err=True)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
def get_job(self, job_name: str) -> Optional[Dict[str, Any]]:
|
|
54
|
+
"""Get a job by name."""
|
|
55
|
+
for job in self.jobs:
|
|
56
|
+
if job.get('name') == job_name:
|
|
57
|
+
return job
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def list_jobs(self) -> List[str]:
|
|
61
|
+
"""Get list of all job names."""
|
|
62
|
+
return [job.get('name', 'unnamed') for job in self.jobs]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def run_job_command(job: Dict[str, Any]) -> int:
|
|
66
|
+
"""Run a job's command with its environment variables."""
|
|
67
|
+
# Get command
|
|
68
|
+
command = job.get('command', [])
|
|
69
|
+
if not command:
|
|
70
|
+
click.echo(f"❌ Error: No command specified for job '{job.get('name')}'", err=True)
|
|
71
|
+
return 1
|
|
72
|
+
|
|
73
|
+
# Prepare environment
|
|
74
|
+
env = os.environ.copy()
|
|
75
|
+
job_env = job.get('env', {})
|
|
76
|
+
|
|
77
|
+
# Convert all env values to strings and merge
|
|
78
|
+
for key, value in job_env.items():
|
|
79
|
+
env[key] = str(value)
|
|
80
|
+
|
|
81
|
+
# Show what we're running
|
|
82
|
+
job_name = job.get('name', 'unnamed')
|
|
83
|
+
click.echo(f"🚀 Running job: {click.style(job_name, fg='blue', bold=True)}")
|
|
84
|
+
click.echo(f"📝 Command: {click.style(' '.join(command), fg='cyan')}")
|
|
85
|
+
|
|
86
|
+
if job_env:
|
|
87
|
+
click.echo(f"🌍 Environment variables:")
|
|
88
|
+
for key, value in job_env.items():
|
|
89
|
+
click.echo(f" {click.style(key, fg='green')}={click.style(str(value), fg='yellow')}")
|
|
90
|
+
|
|
91
|
+
click.echo() # Empty line for readability
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# Run the command
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
command,
|
|
97
|
+
env=env,
|
|
98
|
+
cwd=Path.cwd(),
|
|
99
|
+
)
|
|
100
|
+
return result.returncode
|
|
101
|
+
|
|
102
|
+
except FileNotFoundError:
|
|
103
|
+
click.echo(f"❌ Error: Command not found: {command[0]}", err=True)
|
|
104
|
+
return 127
|
|
105
|
+
except Exception as e:
|
|
106
|
+
click.echo(f"❌ Error running command: {e}", err=True)
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@click.group(invoke_without_command=True)
|
|
111
|
+
@click.option('--jobs-file', '-f', type=click.Path(exists=True, path_type=Path),
|
|
112
|
+
help='Path to jobs.yaml file (default: ./jobs.yaml)')
|
|
113
|
+
@click.argument('job_name', required=False)
|
|
114
|
+
@click.pass_context
|
|
115
|
+
def main(ctx: click.Context, jobs_file: Optional[Path], job_name: Optional[str]):
|
|
116
|
+
"""Steve CLI - Run jobs from jobs.yaml with proper environment setup."""
|
|
117
|
+
|
|
118
|
+
# Handle the case where a job name is provided directly
|
|
119
|
+
if job_name:
|
|
120
|
+
config = JobsConfig(jobs_file)
|
|
121
|
+
|
|
122
|
+
# Special cases for built-in commands
|
|
123
|
+
if job_name in ['ls', 'list']:
|
|
124
|
+
list_jobs_command(config)
|
|
125
|
+
return
|
|
126
|
+
elif job_name in ['help', '--help', '-h']:
|
|
127
|
+
click.echo(ctx.get_help())
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Try to run the job
|
|
131
|
+
job = config.get_job(job_name)
|
|
132
|
+
if not job:
|
|
133
|
+
available_jobs = config.list_jobs()
|
|
134
|
+
click.echo(f"❌ Error: Job '{job_name}' not found", err=True)
|
|
135
|
+
if available_jobs:
|
|
136
|
+
click.echo(f"Available jobs: {', '.join(available_jobs)}", err=True)
|
|
137
|
+
else:
|
|
138
|
+
click.echo("No jobs found in jobs.yaml", err=True)
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
exit_code = run_job_command(job)
|
|
142
|
+
sys.exit(exit_code)
|
|
143
|
+
|
|
144
|
+
# If no job name provided, show help
|
|
145
|
+
click.echo(ctx.get_help())
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@main.command('ls')
|
|
149
|
+
@click.option('--jobs-file', '-f', type=click.Path(exists=True, path_type=Path),
|
|
150
|
+
help='Path to jobs.yaml file (default: ./jobs.yaml)')
|
|
151
|
+
def list_command(jobs_file: Optional[Path]):
|
|
152
|
+
"""List all available jobs."""
|
|
153
|
+
config = JobsConfig(jobs_file)
|
|
154
|
+
list_jobs_command(config)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def list_jobs_command(config: JobsConfig):
|
|
158
|
+
"""Show list of available jobs with details."""
|
|
159
|
+
jobs = config.list_jobs()
|
|
160
|
+
|
|
161
|
+
if not jobs:
|
|
162
|
+
click.echo("❌ No jobs found in jobs.yaml")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
click.echo(f"📋 Available jobs in {click.style(str(config.jobs_file), fg='cyan')}:")
|
|
166
|
+
click.echo()
|
|
167
|
+
|
|
168
|
+
for job_data in config.jobs:
|
|
169
|
+
name = job_data.get('name', 'unnamed')
|
|
170
|
+
command = job_data.get('command', [])
|
|
171
|
+
cron = job_data.get('cron')
|
|
172
|
+
depends_on = job_data.get('dependsOn', [])
|
|
173
|
+
env_vars = job_data.get('env', {})
|
|
174
|
+
|
|
175
|
+
# Job name
|
|
176
|
+
click.echo(f" {click.style(name, fg='blue', bold=True)}")
|
|
177
|
+
|
|
178
|
+
# Command
|
|
179
|
+
if command:
|
|
180
|
+
click.echo(f" Command: {click.style(' '.join(command), fg='cyan')}")
|
|
181
|
+
|
|
182
|
+
# Schedule
|
|
183
|
+
if cron:
|
|
184
|
+
click.echo(f" Schedule: {click.style(cron, fg='yellow')}")
|
|
185
|
+
|
|
186
|
+
# Dependencies
|
|
187
|
+
if depends_on:
|
|
188
|
+
click.echo(f" Depends on: {click.style(', '.join(depends_on), fg='magenta')}")
|
|
189
|
+
|
|
190
|
+
# Environment variables
|
|
191
|
+
if env_vars:
|
|
192
|
+
click.echo(f" Environment: {click.style(f'{len(env_vars)} variables', fg='green')}")
|
|
193
|
+
for key, value in env_vars.items():
|
|
194
|
+
click.echo(f" {key}={value}")
|
|
195
|
+
|
|
196
|
+
click.echo() # Empty line between jobs
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@main.command('help')
|
|
200
|
+
def help_command():
|
|
201
|
+
"""Show help information."""
|
|
202
|
+
click.echo(__doc__)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == '__main__':
|
|
206
|
+
main()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steve-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple CLI tool to run jobs from jobs.yaml with proper environment setup
|
|
5
|
+
Author: Frank
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/7frank/ds-steve-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/7frank/ds-steve-cli
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: click>=8.0.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
25
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# Steve CLI
|
|
28
|
+
|
|
29
|
+
A simple CLI tool to run jobs from `jobs.yaml` with proper environment setup. Perfect for local development and testing of automation kernel jobs.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### Install from local development
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd /home/frank/Projects/7frank/tilt-ts-4/apps/example-hehnke/packages/steve-cli
|
|
37
|
+
uv venv
|
|
38
|
+
uv pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Install from GitHub
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv add git+https://github.com/your-org/tilt-ts-4.git#subdirectory=apps/example-hehnke/packages/steve-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Run a job
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
steve extract-data
|
|
53
|
+
steve transform-data
|
|
54
|
+
steve validate-data
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This will:
|
|
58
|
+
1. Look for `jobs.yaml` in the current directory
|
|
59
|
+
2. Find the specified job by name
|
|
60
|
+
3. Set all environment variables from the job's `env` section
|
|
61
|
+
4. Execute the job's command with the proper environment
|
|
62
|
+
|
|
63
|
+
### List available jobs
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
steve ls
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Shows all jobs in `jobs.yaml` with their commands, schedules, dependencies, and environment variables.
|
|
70
|
+
|
|
71
|
+
### Get help
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
steve help
|
|
75
|
+
steve --help
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Use custom jobs file
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
steve -f path/to/custom.yaml extract-data
|
|
82
|
+
steve ls -f path/to/custom.yaml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Example
|
|
86
|
+
|
|
87
|
+
Given a `jobs.yaml` file:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
jobs:
|
|
91
|
+
- name: extract-data
|
|
92
|
+
cron: "0 2 * * *"
|
|
93
|
+
command: ["uv", "run", "src/extract.py"]
|
|
94
|
+
env:
|
|
95
|
+
SOURCE_URL: "https://jsonplaceholder.typicode.com/users"
|
|
96
|
+
OUTPUT_PATH: "/data/raw/extracted_data.parquet"
|
|
97
|
+
|
|
98
|
+
- name: transform-data
|
|
99
|
+
dependsOn: ["extract-data"]
|
|
100
|
+
command: ["uv", "run", "src/transform.py"]
|
|
101
|
+
env:
|
|
102
|
+
INPUT_PATH: "/data/raw/extracted_data.parquet"
|
|
103
|
+
OUTPUT_PATH: "/data/processed"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Running `steve extract-data` will:
|
|
107
|
+
1. Set `SOURCE_URL=https://jsonplaceholder.typicode.com/users`
|
|
108
|
+
2. Set `OUTPUT_PATH=/data/raw/extracted_data.parquet`
|
|
109
|
+
3. Execute: `uv run src/extract.py`
|
|
110
|
+
|
|
111
|
+
## Features
|
|
112
|
+
|
|
113
|
+
- ✅ **Simple**: Just `steve <job-name>` to run any job
|
|
114
|
+
- ✅ **Environment**: Automatically sets environment variables from `jobs.yaml`
|
|
115
|
+
- ✅ **Discovery**: Auto-finds `jobs.yaml` in current directory
|
|
116
|
+
- ✅ **Listing**: See all available jobs with `steve ls`
|
|
117
|
+
- ✅ **Flexible**: Supports custom jobs file paths
|
|
118
|
+
- ✅ **Colorful**: Nice colored output for better readability
|
|
119
|
+
- ✅ **Error handling**: Clear error messages for missing jobs or files
|
|
120
|
+
|
|
121
|
+
## Why Steve?
|
|
122
|
+
|
|
123
|
+
Named after Steve Jobs - because it helps you run **jobs** locally! 😄
|
|
124
|
+
|
|
125
|
+
## Requirements
|
|
126
|
+
|
|
127
|
+
- Python 3.8+
|
|
128
|
+
- click >= 8.0.0
|
|
129
|
+
- pyyaml >= 6.0
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Install in development mode
|
|
135
|
+
uv pip install -e .
|
|
136
|
+
|
|
137
|
+
# Run tests (when added)
|
|
138
|
+
pytest
|
|
139
|
+
|
|
140
|
+
# Format code
|
|
141
|
+
black steve_cli/
|
|
142
|
+
isort steve_cli/
|
|
143
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
steve_cli/__init__.py,sha256=9ZNbyxRn5yhpwQWw7C549nt9D-i-RkOw2dUUTRzlGlw,19
|
|
2
|
+
steve_cli/cli.py,sha256=iRNHL0v_5Kz_DPMo0EwSpqdrVTMaNh-1tqyN5r0zV60,6751
|
|
3
|
+
steve_cli-0.1.0.dist-info/METADATA,sha256=6IUkSx52TpV3_tJaDZS95JvL2ihaakeGSyU0lzmnoEc,3550
|
|
4
|
+
steve_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
steve_cli-0.1.0.dist-info/entry_points.txt,sha256=-3uaEJ5HlIVb_tm78oQWSCp_P3O2nO5E2xkrQnJHWfE,45
|
|
6
|
+
steve_cli-0.1.0.dist-info/top_level.txt,sha256=6Qvx-KOimoeaGyjF9PWMD3KNjcaR_ik5bgxlGCnrCjs,10
|
|
7
|
+
steve_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
steve_cli
|