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.
Files changed (55) hide show
  1. steve_cli-0.5.2/PKG-INFO +283 -0
  2. steve_cli-0.5.2/README.md +218 -0
  3. {steve_cli-0.3.20 → steve_cli-0.5.2}/pyproject.toml +9 -1
  4. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/cli.py +126 -0
  5. steve_cli-0.5.2/steve_cli/decorators/__init__.py +10 -0
  6. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/decorators/lineage_job.py +13 -2
  7. steve_cli-0.5.2/steve_cli/lineage/adapters/composite.py +12 -0
  8. steve_cli-0.5.2/steve_cli/lineage/adapters/dataproductregistry.py +42 -0
  9. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/registry.py +9 -0
  10. steve_cli-0.5.2/steve_cli/ontology.py +1895 -0
  11. steve_cli-0.5.2/steve_cli/policies/__init__.py +3 -0
  12. steve_cli-0.5.2/steve_cli/policies/client.py +89 -0
  13. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/__init__.py +2 -0
  14. steve_cli-0.5.2/steve_cli/storage/trino.py +208 -0
  15. steve_cli-0.5.2/steve_cli/vkg.py +173 -0
  16. steve_cli-0.5.2/steve_cli.egg-info/PKG-INFO +283 -0
  17. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/SOURCES.txt +7 -0
  18. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/requires.txt +8 -0
  19. steve_cli-0.3.20/PKG-INFO +0 -242
  20. steve_cli-0.3.20/README.md +0 -184
  21. steve_cli-0.3.20/steve_cli/decorators/__init__.py +0 -3
  22. steve_cli-0.3.20/steve_cli.egg-info/PKG-INFO +0 -242
  23. {steve_cli-0.3.20 → steve_cli-0.5.2}/setup.cfg +0 -0
  24. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/__init__.py +0 -0
  25. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/__init__.py +0 -0
  26. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/__init__.py +0 -0
  27. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/logging.py +0 -0
  28. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/null.py +0 -0
  29. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/adapters/openlineage.py +0 -0
  30. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/collector.py +0 -0
  31. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/port.py +0 -0
  32. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/lineage/storage.py +0 -0
  33. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/__init__.py +0 -0
  34. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/__init__.py +0 -0
  35. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/csv.py +0 -0
  36. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/excel.py +0 -0
  37. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/generic.py +0 -0
  38. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/json.py +0 -0
  39. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/extractors/parquet.py +0 -0
  40. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/port.py +0 -0
  41. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/metadata/registry.py +0 -0
  42. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/parquet.py +0 -0
  43. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/protocol.py +0 -0
  44. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage/s3.py +0 -0
  45. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/storage.py +0 -0
  46. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/__init__.py +0 -0
  47. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/__init__.py +0 -0
  48. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/great_expectations.py +0 -0
  49. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/null.py +0 -0
  50. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/adapters/validoopsie.py +0 -0
  51. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/port.py +0 -0
  52. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli/validation/registry.py +0 -0
  53. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/dependency_links.txt +0 -0
  54. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/entry_points.txt +0 -0
  55. {steve_cli-0.3.20 → steve_cli-0.5.2}/steve_cli.egg-info/top_level.txt +0 -0
@@ -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.3.20"
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"]