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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ steve = steve_cli.cli:main
@@ -0,0 +1 @@
1
+ steve_cli