steve-cli 0.3.20__tar.gz → 0.5.2__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.
- steve_cli-0.5.2/PKG-INFO +283 -0
- steve_cli-0.5.2/README.md +218 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/pyproject.toml +9 -1
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/cli.py +126 -0
- steve_cli-0.5.2/steve_cli/decorators/__init__.py +10 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/decorators/lineage_job.py +13 -2
- steve_cli-0.5.2/steve_cli/lineage/adapters/composite.py +12 -0
- steve_cli-0.5.2/steve_cli/lineage/adapters/dataproductregistry.py +42 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/registry.py +9 -0
- steve_cli-0.5.2/steve_cli/ontology.py +1895 -0
- steve_cli-0.5.2/steve_cli/policies/__init__.py +3 -0
- steve_cli-0.5.2/steve_cli/policies/client.py +89 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/__init__.py +2 -0
- steve_cli-0.5.2/steve_cli/storage/trino.py +208 -0
- steve_cli-0.5.2/steve_cli/vkg.py +173 -0
- steve_cli-0.5.2/steve_cli.egg-info/PKG-INFO +283 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/SOURCES.txt +7 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/requires.txt +8 -0
- steve_cli-0.3.20/PKG-INFO +0 -242
- steve_cli-0.3.20/README.md +0 -184
- steve_cli-0.3.20/steve_cli/decorators/__init__.py +0 -3
- steve_cli-0.3.20/steve_cli.egg-info/PKG-INFO +0 -242
- {steve_cli-0.3.20 → steve_cli-0.5.2}/setup.cfg +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/logging.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/null.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/openlineage.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/collector.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/port.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/storage.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/csv.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/excel.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/generic.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/json.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/parquet.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/port.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/registry.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/parquet.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/protocol.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/s3.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/__init__.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/great_expectations.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/null.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/validoopsie.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/port.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/registry.py +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/dependency_links.txt +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/entry_points.txt +0 -0
- {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/top_level.txt +0 -0
steve_cli-0.5.2/PKG-INFO
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steve-cli
|
|
3
|
+
Version: 0.5.2
|
|
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.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: boto3>=1.37.38
|
|
19
|
+
Requires-Dist: click>=8.0.0
|
|
20
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Requires-Dist: questionary>=2.0.0
|
|
23
|
+
Requires-Dist: rdflib>=6.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
27
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
28
|
+
Requires-Dist: polars>=1.8.2; extra == "dev"
|
|
29
|
+
Requires-Dist: pyarrow>=17.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: validoopsie>=0.1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: openlineage-python>=1.0.0; extra == "dev"
|
|
32
|
+
Provides-Extra: polars
|
|
33
|
+
Requires-Dist: polars>=1.8.2; extra == "polars"
|
|
34
|
+
Requires-Dist: pyarrow>=17.0.0; extra == "polars"
|
|
35
|
+
Provides-Extra: pandas
|
|
36
|
+
Requires-Dist: pandas>=2.0.3; extra == "pandas"
|
|
37
|
+
Requires-Dist: pyarrow>=17.0.0; extra == "pandas"
|
|
38
|
+
Provides-Extra: lineage
|
|
39
|
+
Requires-Dist: openlineage-python>=1.0.0; extra == "lineage"
|
|
40
|
+
Provides-Extra: validoopsie
|
|
41
|
+
Requires-Dist: validoopsie>=0.1.0; extra == "validoopsie"
|
|
42
|
+
Requires-Dist: polars>=1.8.2; extra == "validoopsie"
|
|
43
|
+
Provides-Extra: great-expectations
|
|
44
|
+
Requires-Dist: great-expectations>=0.18.0; extra == "great-expectations"
|
|
45
|
+
Requires-Dist: pandas>=2.0.3; extra == "great-expectations"
|
|
46
|
+
Provides-Extra: excel
|
|
47
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "excel"
|
|
48
|
+
Provides-Extra: trino
|
|
49
|
+
Requires-Dist: trino>=0.330; extra == "trino"
|
|
50
|
+
Requires-Dist: pyarrow>=17.0.0; extra == "trino"
|
|
51
|
+
Requires-Dist: requests>=2.31.0; extra == "trino"
|
|
52
|
+
Requires-Dist: pyiceberg>=0.7.0; extra == "trino"
|
|
53
|
+
Requires-Dist: s3fs>=2024.1.0; extra == "trino"
|
|
54
|
+
Provides-Extra: visidata
|
|
55
|
+
Requires-Dist: visidata>=3.0; extra == "visidata"
|
|
56
|
+
Provides-Extra: all
|
|
57
|
+
Requires-Dist: openlineage-python>=1.0.0; extra == "all"
|
|
58
|
+
Requires-Dist: pyarrow>=17.0.0; extra == "all"
|
|
59
|
+
Requires-Dist: polars>=1.8.2; extra == "all"
|
|
60
|
+
Requires-Dist: validoopsie>=0.1.0; extra == "all"
|
|
61
|
+
Requires-Dist: great-expectations>=0.18.0; extra == "all"
|
|
62
|
+
Requires-Dist: pandas>=2.0.3; extra == "all"
|
|
63
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "all"
|
|
64
|
+
Requires-Dist: visidata>=3.0; extra == "all"
|
|
65
|
+
|
|
66
|
+
# Steve CLI
|
|
67
|
+
|
|
68
|
+
A CLI tool for the data platform. Commands are organized by concern: general workspace automation, data lakehouse operations, semantic knowledge graphs, and governance.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Add to your workspace
|
|
74
|
+
uv add steve-cli
|
|
75
|
+
|
|
76
|
+
# Or pin to a local editable copy (in your workspace pyproject.toml)
|
|
77
|
+
[tool.uv.sources]
|
|
78
|
+
steve-cli = { path = "../../packages/steve-cli", editable = true }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Upgrade to the latest version:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
steve upgrade
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Platform
|
|
90
|
+
|
|
91
|
+
General-purpose commands for running jobs, managing local development processes, and working with the core infrastructure (object storage, secrets).
|
|
92
|
+
|
|
93
|
+
### `steve jobs` — run automation jobs
|
|
94
|
+
|
|
95
|
+
Runs tasks defined in `jobs.yaml`. Each job declares a command and optional env vars. Designed for data pipeline steps, seeding, bootstrapping, etc.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
steve jobs # interactive picker
|
|
99
|
+
steve jobs ls # list all jobs with commands and env vars
|
|
100
|
+
steve jobs run <name> # run a job by name
|
|
101
|
+
steve jobs run <name> -f path/to/jobs.yaml
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`jobs.yaml` example:
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
jobs:
|
|
108
|
+
- name: seed-data
|
|
109
|
+
command: ["uv", "run", "src/02_seed_data.py"]
|
|
110
|
+
env:
|
|
111
|
+
BATCH_SIZE: "500"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `steve apps` — manage long-running apps
|
|
115
|
+
|
|
116
|
+
Starts, stops, and monitors local apps defined in `apps.yaml`. Apps are registered with the auth-proxy so they get a public URL inside the platform.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
steve apps # interactive picker
|
|
120
|
+
steve apps ls # list apps with status and URLs
|
|
121
|
+
steve apps status # show running/stopped status for all apps
|
|
122
|
+
steve apps start <name>
|
|
123
|
+
steve apps stop <name>
|
|
124
|
+
steve apps restart <name>
|
|
125
|
+
steve apps logs <name> [-n 100]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `steve setup env` — decrypt secrets
|
|
129
|
+
|
|
130
|
+
Decrypts SOPS-encrypted `*.enc.env` files in the current directory and writes plaintext `.env` files.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
steve setup env
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `steve buckets` — browse S3 storage
|
|
137
|
+
|
|
138
|
+
Interactive browser for S3/MinIO buckets. Detects available buckets from env vars (`BRONZE_ACCESS_KEY`, `SILVER_ACCESS_KEY`, `GOLD_ACCESS_KEY`, or `{WORKSPACE}_ACCESS_KEY`) and shows their contents as a file tree. Files can be opened in visidata.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
steve buckets
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Data Lakehouse
|
|
147
|
+
|
|
148
|
+
Commands for working with the lakehouse layer: Iceberg tables accessed via Trino.
|
|
149
|
+
|
|
150
|
+
### `steve tables` — browse Iceberg tables via Trino
|
|
151
|
+
|
|
152
|
+
Interactive browser for Iceberg tables. Lists schemas and tables available via Trino, and opens them in visidata for inspection.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
steve tables
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Requires `TRINO_ENDPOINT` to be set. Install visidata for table preview:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
uv pip install 'steve-cli[visidata]'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Semantics & Knowledge Graphs
|
|
167
|
+
|
|
168
|
+
Commands for authoring and operating Virtual Knowledge Graphs (VKGs) — semantic layers that expose relational Iceberg data as RDF/SPARQL via Ontop.
|
|
169
|
+
|
|
170
|
+
There are two sides:
|
|
171
|
+
- **`ontology`** — the *author/publish* side: define concepts, register them in the Data Product Registry, compile VKG artifacts
|
|
172
|
+
- **`vkg`** — the *runtime/ops* side: manage named Ontop instances on the VKG control plane, run SPARQL queries
|
|
173
|
+
|
|
174
|
+
### `steve ontology` — author and publish ontologies
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
steve ontology ls # list registered packages in the registry
|
|
178
|
+
steve ontology ls --show-bindings # include binding packages
|
|
179
|
+
steve ontology register <file.yaml> # register a concept or binding YAML
|
|
180
|
+
steve ontology add <uri> # add a dependency URI to ontology.yaml
|
|
181
|
+
steve ontology build # resolve deps + compile → write vkg/ folder locally
|
|
182
|
+
steve ontology compile --root <uri> --binding <uri> # compile and print artifact info
|
|
183
|
+
steve ontology push # compile + upload to VKG control plane (default slot)
|
|
184
|
+
steve ontology push --name <slot> # push to a named VKG slot
|
|
185
|
+
steve ontology push -f ontology-consumer.yaml # use a different manifest file
|
|
186
|
+
steve ontology push --frozen # fail if lock file is absent or stale (like npm ci)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Manifest format (`ontology.yaml`):
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
# Option A: workspace that owns ontology files
|
|
193
|
+
packages:
|
|
194
|
+
- path: ontologies/order_core.yaml
|
|
195
|
+
bindings:
|
|
196
|
+
- path: ontologies/binding_order_iceberg.yaml
|
|
197
|
+
compile:
|
|
198
|
+
root: dp://o/acme/concepts/order/core
|
|
199
|
+
binding: dp://o/acme/bindings/order/iceberg
|
|
200
|
+
|
|
201
|
+
# Option B: consumer workspace (resolves deps from registry, no local files)
|
|
202
|
+
dependencies:
|
|
203
|
+
- dp://o/acme/concepts/party/core
|
|
204
|
+
- dp://o/acme/concepts/order/core
|
|
205
|
+
compile:
|
|
206
|
+
root: dp://o/acme/concepts/order/core
|
|
207
|
+
binding: dp://o/acme/bindings/order/iceberg
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
After `push`, a lock file (`ontology.lock.yaml`) is written with resolved package versions and Iceberg snapshot IDs for audit and future time-travel SQL rewriting.
|
|
211
|
+
|
|
212
|
+
### `steve vkg` — operate VKG instances
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
steve vkg ls # list all slots: status, concept counts, SPARQL URL
|
|
216
|
+
steve vkg status <name> # detailed status for one slot
|
|
217
|
+
steve vkg start <name> # start a stopped slot
|
|
218
|
+
steve vkg stop <name> # stop a running slot (persists desired=stopped)
|
|
219
|
+
steve vkg sparql <name> "<SPARQL query>" # run a SPARQL query and print results as a table
|
|
220
|
+
steve vkg sparql <name> "<query>" --format json
|
|
221
|
+
steve vkg logs <name> [-n 50] # tail the Ontop process log for a slot
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Desired state is persisted per slot — slots with `desired=running` are automatically restored after a pod restart.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Governance
|
|
229
|
+
|
|
230
|
+
### `steve policies apply` — apply access policies
|
|
231
|
+
|
|
232
|
+
Applies role-based access policies from a YAML file to the Policy Control Plane.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
steve policies apply # apply from ./policies/access.yaml
|
|
236
|
+
steve policies apply -f custom/path.yaml
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Environment variables
|
|
242
|
+
|
|
243
|
+
| Variable | Used by | Description |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| `REGISTRY_URL` | `ontology` | Data Product Registry URL (default: `http://localhost:8765`) |
|
|
246
|
+
| `REGISTRY_TOKEN` | `ontology` | Pre-issued JWT for the registry |
|
|
247
|
+
| `REGISTRY_JWT_SECRET` | `ontology` | Secret to mint a JWT (if no token) |
|
|
248
|
+
| `REGISTRY_ORG` | `ontology` | Org claim for minted JWT |
|
|
249
|
+
| `VKG_CONTROL_PLANE_URL` | `ontology push`, `vkg` | Control plane URL (default: `http://localhost:18081`) |
|
|
250
|
+
| `TRINO_ENDPOINT` | `ontology push`, `tables` | Trino HTTP URL for snapshot capture and table browsing |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## File Metadata Extraction
|
|
255
|
+
|
|
256
|
+
Steve automatically extracts metadata from files read or written via `MetadataRegistry`. The extractor is chosen by file extension.
|
|
257
|
+
|
|
258
|
+
| Extension | Extractor | Requires |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `.parquet`, `.pq` | `ParquetExtractor` | `pip install steve-cli[polars]` |
|
|
261
|
+
| `.csv`, `.tsv`, `.txt` | `CsvExtractor` | stdlib only |
|
|
262
|
+
| `.json`, `.jsonl`, `.ndjson` | `JsonExtractor` | stdlib only |
|
|
263
|
+
| `.xlsx`, `.xls`, `.xlsm` | `ExcelExtractor` | `pip install steve-cli[excel]` |
|
|
264
|
+
| anything else | `GenericExtractor` | stdlib only |
|
|
265
|
+
|
|
266
|
+
Custom extractors can be registered at startup:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from steve_cli.storage.metadata.port import MetadataExtractorPort, FileMetadata, ColumnMetadata
|
|
270
|
+
from steve_cli.storage.metadata.registry import MetadataRegistry
|
|
271
|
+
|
|
272
|
+
class AvroExtractor(MetadataExtractorPort):
|
|
273
|
+
extensions = (".avro",)
|
|
274
|
+
|
|
275
|
+
def extract(self, data: bytes, path: str) -> FileMetadata:
|
|
276
|
+
import fastavro, io
|
|
277
|
+
reader = fastavro.reader(io.BytesIO(data))
|
|
278
|
+
schema = reader.writer_schema
|
|
279
|
+
columns = [ColumnMetadata(name=f["name"], type=str(f["type"])) for f in schema.get("fields", [])]
|
|
280
|
+
return FileMetadata(format="avro", size_bytes=len(data), rows=len(list(reader)), columns=columns)
|
|
281
|
+
|
|
282
|
+
MetadataRegistry.register("avro", AvroExtractor)
|
|
283
|
+
```
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Steve CLI
|
|
2
|
+
|
|
3
|
+
A CLI tool for the data platform. Commands are organized by concern: general workspace automation, data lakehouse operations, semantic knowledge graphs, and governance.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Add to your workspace
|
|
9
|
+
uv add steve-cli
|
|
10
|
+
|
|
11
|
+
# Or pin to a local editable copy (in your workspace pyproject.toml)
|
|
12
|
+
[tool.uv.sources]
|
|
13
|
+
steve-cli = { path = "../../packages/steve-cli", editable = true }
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Upgrade to the latest version:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
steve upgrade
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Platform
|
|
25
|
+
|
|
26
|
+
General-purpose commands for running jobs, managing local development processes, and working with the core infrastructure (object storage, secrets).
|
|
27
|
+
|
|
28
|
+
### `steve jobs` — run automation jobs
|
|
29
|
+
|
|
30
|
+
Runs tasks defined in `jobs.yaml`. Each job declares a command and optional env vars. Designed for data pipeline steps, seeding, bootstrapping, etc.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
steve jobs # interactive picker
|
|
34
|
+
steve jobs ls # list all jobs with commands and env vars
|
|
35
|
+
steve jobs run <name> # run a job by name
|
|
36
|
+
steve jobs run <name> -f path/to/jobs.yaml
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`jobs.yaml` example:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
jobs:
|
|
43
|
+
- name: seed-data
|
|
44
|
+
command: ["uv", "run", "src/02_seed_data.py"]
|
|
45
|
+
env:
|
|
46
|
+
BATCH_SIZE: "500"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `steve apps` — manage long-running apps
|
|
50
|
+
|
|
51
|
+
Starts, stops, and monitors local apps defined in `apps.yaml`. Apps are registered with the auth-proxy so they get a public URL inside the platform.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
steve apps # interactive picker
|
|
55
|
+
steve apps ls # list apps with status and URLs
|
|
56
|
+
steve apps status # show running/stopped status for all apps
|
|
57
|
+
steve apps start <name>
|
|
58
|
+
steve apps stop <name>
|
|
59
|
+
steve apps restart <name>
|
|
60
|
+
steve apps logs <name> [-n 100]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `steve setup env` — decrypt secrets
|
|
64
|
+
|
|
65
|
+
Decrypts SOPS-encrypted `*.enc.env` files in the current directory and writes plaintext `.env` files.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
steve setup env
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `steve buckets` — browse S3 storage
|
|
72
|
+
|
|
73
|
+
Interactive browser for S3/MinIO buckets. Detects available buckets from env vars (`BRONZE_ACCESS_KEY`, `SILVER_ACCESS_KEY`, `GOLD_ACCESS_KEY`, or `{WORKSPACE}_ACCESS_KEY`) and shows their contents as a file tree. Files can be opened in visidata.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
steve buckets
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Data Lakehouse
|
|
82
|
+
|
|
83
|
+
Commands for working with the lakehouse layer: Iceberg tables accessed via Trino.
|
|
84
|
+
|
|
85
|
+
### `steve tables` — browse Iceberg tables via Trino
|
|
86
|
+
|
|
87
|
+
Interactive browser for Iceberg tables. Lists schemas and tables available via Trino, and opens them in visidata for inspection.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
steve tables
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Requires `TRINO_ENDPOINT` to be set. Install visidata for table preview:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv pip install 'steve-cli[visidata]'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Semantics & Knowledge Graphs
|
|
102
|
+
|
|
103
|
+
Commands for authoring and operating Virtual Knowledge Graphs (VKGs) — semantic layers that expose relational Iceberg data as RDF/SPARQL via Ontop.
|
|
104
|
+
|
|
105
|
+
There are two sides:
|
|
106
|
+
- **`ontology`** — the *author/publish* side: define concepts, register them in the Data Product Registry, compile VKG artifacts
|
|
107
|
+
- **`vkg`** — the *runtime/ops* side: manage named Ontop instances on the VKG control plane, run SPARQL queries
|
|
108
|
+
|
|
109
|
+
### `steve ontology` — author and publish ontologies
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
steve ontology ls # list registered packages in the registry
|
|
113
|
+
steve ontology ls --show-bindings # include binding packages
|
|
114
|
+
steve ontology register <file.yaml> # register a concept or binding YAML
|
|
115
|
+
steve ontology add <uri> # add a dependency URI to ontology.yaml
|
|
116
|
+
steve ontology build # resolve deps + compile → write vkg/ folder locally
|
|
117
|
+
steve ontology compile --root <uri> --binding <uri> # compile and print artifact info
|
|
118
|
+
steve ontology push # compile + upload to VKG control plane (default slot)
|
|
119
|
+
steve ontology push --name <slot> # push to a named VKG slot
|
|
120
|
+
steve ontology push -f ontology-consumer.yaml # use a different manifest file
|
|
121
|
+
steve ontology push --frozen # fail if lock file is absent or stale (like npm ci)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Manifest format (`ontology.yaml`):
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
# Option A: workspace that owns ontology files
|
|
128
|
+
packages:
|
|
129
|
+
- path: ontologies/order_core.yaml
|
|
130
|
+
bindings:
|
|
131
|
+
- path: ontologies/binding_order_iceberg.yaml
|
|
132
|
+
compile:
|
|
133
|
+
root: dp://o/acme/concepts/order/core
|
|
134
|
+
binding: dp://o/acme/bindings/order/iceberg
|
|
135
|
+
|
|
136
|
+
# Option B: consumer workspace (resolves deps from registry, no local files)
|
|
137
|
+
dependencies:
|
|
138
|
+
- dp://o/acme/concepts/party/core
|
|
139
|
+
- dp://o/acme/concepts/order/core
|
|
140
|
+
compile:
|
|
141
|
+
root: dp://o/acme/concepts/order/core
|
|
142
|
+
binding: dp://o/acme/bindings/order/iceberg
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
After `push`, a lock file (`ontology.lock.yaml`) is written with resolved package versions and Iceberg snapshot IDs for audit and future time-travel SQL rewriting.
|
|
146
|
+
|
|
147
|
+
### `steve vkg` — operate VKG instances
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
steve vkg ls # list all slots: status, concept counts, SPARQL URL
|
|
151
|
+
steve vkg status <name> # detailed status for one slot
|
|
152
|
+
steve vkg start <name> # start a stopped slot
|
|
153
|
+
steve vkg stop <name> # stop a running slot (persists desired=stopped)
|
|
154
|
+
steve vkg sparql <name> "<SPARQL query>" # run a SPARQL query and print results as a table
|
|
155
|
+
steve vkg sparql <name> "<query>" --format json
|
|
156
|
+
steve vkg logs <name> [-n 50] # tail the Ontop process log for a slot
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Desired state is persisted per slot — slots with `desired=running` are automatically restored after a pod restart.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Governance
|
|
164
|
+
|
|
165
|
+
### `steve policies apply` — apply access policies
|
|
166
|
+
|
|
167
|
+
Applies role-based access policies from a YAML file to the Policy Control Plane.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
steve policies apply # apply from ./policies/access.yaml
|
|
171
|
+
steve policies apply -f custom/path.yaml
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Environment variables
|
|
177
|
+
|
|
178
|
+
| Variable | Used by | Description |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `REGISTRY_URL` | `ontology` | Data Product Registry URL (default: `http://localhost:8765`) |
|
|
181
|
+
| `REGISTRY_TOKEN` | `ontology` | Pre-issued JWT for the registry |
|
|
182
|
+
| `REGISTRY_JWT_SECRET` | `ontology` | Secret to mint a JWT (if no token) |
|
|
183
|
+
| `REGISTRY_ORG` | `ontology` | Org claim for minted JWT |
|
|
184
|
+
| `VKG_CONTROL_PLANE_URL` | `ontology push`, `vkg` | Control plane URL (default: `http://localhost:18081`) |
|
|
185
|
+
| `TRINO_ENDPOINT` | `ontology push`, `tables` | Trino HTTP URL for snapshot capture and table browsing |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## File Metadata Extraction
|
|
190
|
+
|
|
191
|
+
Steve automatically extracts metadata from files read or written via `MetadataRegistry`. The extractor is chosen by file extension.
|
|
192
|
+
|
|
193
|
+
| Extension | Extractor | Requires |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `.parquet`, `.pq` | `ParquetExtractor` | `pip install steve-cli[polars]` |
|
|
196
|
+
| `.csv`, `.tsv`, `.txt` | `CsvExtractor` | stdlib only |
|
|
197
|
+
| `.json`, `.jsonl`, `.ndjson` | `JsonExtractor` | stdlib only |
|
|
198
|
+
| `.xlsx`, `.xls`, `.xlsm` | `ExcelExtractor` | `pip install steve-cli[excel]` |
|
|
199
|
+
| anything else | `GenericExtractor` | stdlib only |
|
|
200
|
+
|
|
201
|
+
Custom extractors can be registered at startup:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from steve_cli.storage.metadata.port import MetadataExtractorPort, FileMetadata, ColumnMetadata
|
|
205
|
+
from steve_cli.storage.metadata.registry import MetadataRegistry
|
|
206
|
+
|
|
207
|
+
class AvroExtractor(MetadataExtractorPort):
|
|
208
|
+
extensions = (".avro",)
|
|
209
|
+
|
|
210
|
+
def extract(self, data: bytes, path: str) -> FileMetadata:
|
|
211
|
+
import fastavro, io
|
|
212
|
+
reader = fastavro.reader(io.BytesIO(data))
|
|
213
|
+
schema = reader.writer_schema
|
|
214
|
+
columns = [ColumnMetadata(name=f["name"], type=str(f["type"])) for f in schema.get("fields", [])]
|
|
215
|
+
return FileMetadata(format="avro", size_bytes=len(data), rows=len(list(reader)), columns=columns)
|
|
216
|
+
|
|
217
|
+
MetadataRegistry.register("avro", AvroExtractor)
|
|
218
|
+
```
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "steve-cli"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.5.2"
|
|
9
9
|
description = "A simple CLI tool to run jobs from jobs.yaml with proper environment setup"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = {text = "MIT"}
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
"python-dotenv>=1.0.0",
|
|
27
27
|
"pyyaml>=6.0",
|
|
28
28
|
"questionary>=2.0.0",
|
|
29
|
+
"rdflib>=6.0",
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
[project.optional-dependencies]
|
|
@@ -60,6 +61,13 @@ great-expectations = [
|
|
|
60
61
|
excel = [
|
|
61
62
|
"openpyxl>=3.1.0",
|
|
62
63
|
]
|
|
64
|
+
trino = [
|
|
65
|
+
"trino>=0.330",
|
|
66
|
+
"pyarrow>=17.0.0",
|
|
67
|
+
"requests>=2.31.0",
|
|
68
|
+
"pyiceberg>=0.7.0",
|
|
69
|
+
"s3fs>=2024.1.0",
|
|
70
|
+
]
|
|
63
71
|
visidata = [
|
|
64
72
|
"visidata>=3.0",
|
|
65
73
|
]
|
|
@@ -781,6 +781,126 @@ def buckets(env_file: tuple):
|
|
|
781
781
|
click.secho(f"❌ Could not open file: {e}", fg="red", err=True)
|
|
782
782
|
|
|
783
783
|
|
|
784
|
+
def _list_trino_tables(storage_kwargs: dict, label: str) -> tuple:
|
|
785
|
+
click.echo(f" {click.style(label, fg='cyan')}")
|
|
786
|
+
try:
|
|
787
|
+
from steve_cli.storage.trino import TrinoStorage
|
|
788
|
+
storage = TrinoStorage(**storage_kwargs)
|
|
789
|
+
tables = storage.list()
|
|
790
|
+
if not tables:
|
|
791
|
+
click.echo(" (no tables)")
|
|
792
|
+
else:
|
|
793
|
+
for t in tables:
|
|
794
|
+
click.echo(f" ├── {t}")
|
|
795
|
+
return storage, tables
|
|
796
|
+
except EnvironmentError as e:
|
|
797
|
+
click.echo(f" ⚠️ {e}", err=True)
|
|
798
|
+
except Exception as e:
|
|
799
|
+
click.echo(f" ❌ {e}", err=True)
|
|
800
|
+
return None, []
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
@main.command("tables")
|
|
804
|
+
@click.option('--env-file', '-e', type=click.Path(path_type=Path), multiple=True,
|
|
805
|
+
help='Path to .env file(s). Can be specified multiple times. Defaults to .env and .workspaces.env')
|
|
806
|
+
def tables(env_file: tuple):
|
|
807
|
+
"""List Iceberg tables via Trino and view their contents."""
|
|
808
|
+
if not os.getenv("TRINO_ENDPOINT"):
|
|
809
|
+
click.secho("TRINO_ENDPOINT is not set — Trino is not available.", fg="yellow")
|
|
810
|
+
return
|
|
811
|
+
|
|
812
|
+
cwd = Path.cwd()
|
|
813
|
+
env_files = [Path(f) for f in env_file] if env_file else [cwd / ".env", cwd / ".workspaces.env"]
|
|
814
|
+
for ef in env_files:
|
|
815
|
+
load_dotenv(ef)
|
|
816
|
+
|
|
817
|
+
tiers = ["bronze", "silver", "gold"]
|
|
818
|
+
options: List[Dict[str, Any]] = []
|
|
819
|
+
|
|
820
|
+
bare_tiers = [t for t in tiers if os.getenv(f"{t.upper()}_ACCESS_KEY")]
|
|
821
|
+
for tier in bare_tiers:
|
|
822
|
+
options.append({
|
|
823
|
+
"label": f"default / {tier}",
|
|
824
|
+
"kwargs": {"tier": tier},
|
|
825
|
+
})
|
|
826
|
+
|
|
827
|
+
for ws in _detect_workspaces():
|
|
828
|
+
for tier in tiers:
|
|
829
|
+
if not os.getenv(f"{ws}_ACCESS_KEY_{tier.upper()}") and not os.getenv(f"{ws}_ACCESS_KEY"):
|
|
830
|
+
continue
|
|
831
|
+
options.append({
|
|
832
|
+
"label": f"{ws} / {tier}",
|
|
833
|
+
"kwargs": {"tier": tier, "workspace": ws.lower().replace("_", "-")},
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
if not options:
|
|
837
|
+
click.echo("No Trino storage env variables found (expected: TRINO_ENDPOINT + BRONZE_ACCESS_KEY or {WORKSPACE}_ACCESS_KEY).")
|
|
838
|
+
return
|
|
839
|
+
|
|
840
|
+
choice = questionary.select(
|
|
841
|
+
"Select a schema to list:",
|
|
842
|
+
choices=[o["label"] for o in options],
|
|
843
|
+
).ask()
|
|
844
|
+
|
|
845
|
+
if choice is None:
|
|
846
|
+
sys.exit(0)
|
|
847
|
+
|
|
848
|
+
selected = next(o for o in options if o["label"] == choice)
|
|
849
|
+
storage, table_names = _list_trino_tables(selected["kwargs"], selected["label"])
|
|
850
|
+
|
|
851
|
+
if not storage or not table_names:
|
|
852
|
+
return
|
|
853
|
+
|
|
854
|
+
table_choice = questionary.select(
|
|
855
|
+
"View a table (or press Esc to exit):",
|
|
856
|
+
choices=["(done)"] + table_names,
|
|
857
|
+
).ask()
|
|
858
|
+
|
|
859
|
+
if not table_choice or table_choice == "(done)":
|
|
860
|
+
return
|
|
861
|
+
|
|
862
|
+
click.echo(f"\n📊 {click.style(table_choice, fg='cyan')}\n")
|
|
863
|
+
try:
|
|
864
|
+
import tempfile
|
|
865
|
+
data = storage.get_bytes(table_choice)
|
|
866
|
+
with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as tmp:
|
|
867
|
+
tmp.write(data)
|
|
868
|
+
tmp_path = tmp.name
|
|
869
|
+
if not shutil.which("vd"):
|
|
870
|
+
click.secho("visidata not found. Install it with: uv pip install 'steve-cli[visidata]'", fg="yellow")
|
|
871
|
+
return
|
|
872
|
+
subprocess.call(["vd", tmp_path])
|
|
873
|
+
except Exception as e:
|
|
874
|
+
click.secho(f"❌ Could not open table: {e}", fg="red", err=True)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
@main.group()
|
|
878
|
+
def policies():
|
|
879
|
+
"""Manage and apply access policies via the Policy Control Plane."""
|
|
880
|
+
pass
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
@policies.command("apply")
|
|
884
|
+
@click.option('--file', '-f', type=click.Path(path_type=Path),
|
|
885
|
+
help='Path to policies YAML file (default: ./policies/access.yaml)')
|
|
886
|
+
def policies_apply(file: Path | None):
|
|
887
|
+
"""Apply access policies from a YAML file to the Policy Control Plane."""
|
|
888
|
+
from dotenv import load_dotenv
|
|
889
|
+
load_dotenv(".env", override=False)
|
|
890
|
+
|
|
891
|
+
from steve_cli.policies import PolicyClient
|
|
892
|
+
policies_file = Path(file) if file else None
|
|
893
|
+
try:
|
|
894
|
+
PolicyClient().apply_from_file(policies_file)
|
|
895
|
+
click.secho("Policies applied successfully.", fg="green")
|
|
896
|
+
except FileNotFoundError as e:
|
|
897
|
+
click.secho(str(e), fg="red", err=True)
|
|
898
|
+
sys.exit(1)
|
|
899
|
+
except Exception as e:
|
|
900
|
+
click.secho(f"ERROR: {e}", fg="red", bold=True, err=True)
|
|
901
|
+
sys.exit(1)
|
|
902
|
+
|
|
903
|
+
|
|
784
904
|
@main.command("upgrade")
|
|
785
905
|
def upgrade():
|
|
786
906
|
"""Upgrade steve-cli to the latest version."""
|
|
@@ -793,5 +913,11 @@ def upgrade():
|
|
|
793
913
|
sys.exit(result.returncode)
|
|
794
914
|
|
|
795
915
|
|
|
916
|
+
from steve_cli.ontology import ontology
|
|
917
|
+
from steve_cli.vkg import vkg
|
|
918
|
+
main.add_command(ontology)
|
|
919
|
+
main.add_command(vkg)
|
|
920
|
+
|
|
921
|
+
|
|
796
922
|
if __name__ == '__main__':
|
|
797
923
|
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from steve_cli.lineage.storage import LineageStorage
|
|
4
|
+
|
|
5
|
+
from .lineage_job import lineage_job
|
|
6
|
+
|
|
7
|
+
GetTables = Callable[[str, str | None], LineageStorage]
|
|
8
|
+
GetStorage = Callable[[str, str | None], LineageStorage]
|
|
9
|
+
|
|
10
|
+
__all__ = ["GetStorage", "GetTables", "lineage_job"]
|