steve-cli 0.3.8__tar.gz → 0.3.11__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 (44) hide show
  1. {steve_cli-0.3.8 → steve_cli-0.3.11}/PKG-INFO +4 -1
  2. {steve_cli-0.3.8 → steve_cli-0.3.11}/pyproject.toml +5 -1
  3. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/cli.py +40 -3
  4. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/decorators/lineage_job.py +8 -3
  5. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/collector.py +14 -5
  6. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/storage.py +11 -4
  7. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli.egg-info/PKG-INFO +4 -1
  8. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli.egg-info/requires.txt +4 -0
  9. {steve_cli-0.3.8 → steve_cli-0.3.11}/README.md +0 -0
  10. {steve_cli-0.3.8 → steve_cli-0.3.11}/setup.cfg +0 -0
  11. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/__init__.py +0 -0
  12. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/decorators/__init__.py +0 -0
  13. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/__init__.py +0 -0
  14. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/adapters/__init__.py +0 -0
  15. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/adapters/logging.py +0 -0
  16. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/adapters/null.py +0 -0
  17. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/adapters/openlineage.py +0 -0
  18. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/port.py +0 -0
  19. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/lineage/registry.py +0 -0
  20. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/__init__.py +0 -0
  21. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/__init__.py +0 -0
  22. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/extractors/__init__.py +0 -0
  23. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/extractors/csv.py +0 -0
  24. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/extractors/excel.py +0 -0
  25. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/extractors/generic.py +0 -0
  26. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/extractors/json.py +0 -0
  27. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/extractors/parquet.py +0 -0
  28. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/port.py +0 -0
  29. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/metadata/registry.py +0 -0
  30. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/parquet.py +0 -0
  31. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/protocol.py +0 -0
  32. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage/s3.py +0 -0
  33. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/storage.py +0 -0
  34. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/__init__.py +0 -0
  35. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/adapters/__init__.py +0 -0
  36. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/adapters/great_expectations.py +0 -0
  37. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/adapters/null.py +0 -0
  38. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/adapters/validoopsie.py +0 -0
  39. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/port.py +0 -0
  40. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli/validation/registry.py +0 -0
  41. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli.egg-info/SOURCES.txt +0 -0
  42. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli.egg-info/dependency_links.txt +0 -0
  43. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli.egg-info/entry_points.txt +0 -0
  44. {steve_cli-0.3.8 → steve_cli-0.3.11}/steve_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steve-cli
3
- Version: 0.3.8
3
+ Version: 0.3.11
4
4
  Summary: A simple CLI tool to run jobs from jobs.yaml with proper environment setup
5
5
  Author: Frank
6
6
  License: MIT
@@ -44,6 +44,8 @@ Requires-Dist: great-expectations>=0.18.0; extra == "great-expectations"
44
44
  Requires-Dist: pandas>=2.0.3; extra == "great-expectations"
45
45
  Provides-Extra: excel
46
46
  Requires-Dist: openpyxl>=3.1.0; extra == "excel"
47
+ Provides-Extra: visidata
48
+ Requires-Dist: visidata>=3.0; extra == "visidata"
47
49
  Provides-Extra: all
48
50
  Requires-Dist: openlineage-python>=1.0.0; extra == "all"
49
51
  Requires-Dist: pyarrow>=17.0.0; extra == "all"
@@ -52,6 +54,7 @@ Requires-Dist: validoopsie>=0.1.0; extra == "all"
52
54
  Requires-Dist: great-expectations>=0.18.0; extra == "all"
53
55
  Requires-Dist: pandas>=2.0.3; extra == "all"
54
56
  Requires-Dist: openpyxl>=3.1.0; extra == "all"
57
+ Requires-Dist: visidata>=3.0; extra == "all"
55
58
 
56
59
  # Steve CLI
57
60
 
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "steve-cli"
8
- version = "0.3.8"
8
+ version = "0.3.11"
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"}
@@ -60,6 +60,9 @@ great-expectations = [
60
60
  excel = [
61
61
  "openpyxl>=3.1.0",
62
62
  ]
63
+ visidata = [
64
+ "visidata>=3.0",
65
+ ]
63
66
  all = [
64
67
  "openlineage-python>=1.0.0",
65
68
  "pyarrow>=17.0.0",
@@ -68,6 +71,7 @@ all = [
68
71
  "great-expectations>=0.18.0",
69
72
  "pandas>=2.0.3",
70
73
  "openpyxl>=3.1.0",
74
+ "visidata>=3.0",
71
75
  ]
72
76
 
73
77
  [project.scripts]
@@ -9,6 +9,7 @@ Usage:
9
9
  """
10
10
 
11
11
  import os
12
+ import shutil
12
13
  import signal
13
14
  import socket
14
15
  import subprocess
@@ -18,7 +19,7 @@ from pathlib import Path
18
19
  from typing import Dict, List, Any, Optional
19
20
 
20
21
  import click
21
- from dotenv import load_dotenv
22
+ from dotenv import load_dotenv, dotenv_values
22
23
  import questionary
23
24
  import yaml
24
25
 
@@ -69,6 +70,10 @@ def run_job_command(job: Dict[str, Any]) -> int:
69
70
  return 1
70
71
 
71
72
  env = os.environ.copy()
73
+ for env_filename in [".env", ".workspaces.env"]:
74
+ env_path = Path.cwd() / env_filename
75
+ if env_path.exists():
76
+ env.update({k: v for k, v in dotenv_values(env_path).items() if v is not None})
72
77
  job_env = job.get('env', {})
73
78
  for key, value in job_env.items():
74
79
  env[key] = str(value)
@@ -369,6 +374,10 @@ def apps_start(app_name: str, apps_file: Optional[Path]):
369
374
  sys.exit(1)
370
375
 
371
376
  env = os.environ.copy()
377
+ for env_filename in [".env", ".workspaces.env"]:
378
+ env_path = config.apps_file.parent / env_filename
379
+ if env_path.exists():
380
+ env.update({k: v for k, v in dotenv_values(env_path).items() if v is not None})
372
381
  for key, value in env_vars.items():
373
382
  env[key] = str(value)
374
383
 
@@ -537,7 +546,7 @@ def _print_tree(node: Dict[str, Any], prefix: str = "", is_last: bool = True) ->
537
546
  _print_tree(child, prefix + extension, last)
538
547
 
539
548
 
540
- def _list_bucket(storage_kwargs: dict, label: str, bucket_name: str) -> None:
549
+ def _list_bucket(storage_kwargs: dict, label: str, bucket_name: str) -> tuple:
541
550
  click.echo(f" {click.style(label, fg='cyan')} ({bucket_name})")
542
551
  try:
543
552
  from steve_cli.storage import S3Storage
@@ -548,10 +557,12 @@ def _list_bucket(storage_kwargs: dict, label: str, bucket_name: str) -> None:
548
557
  else:
549
558
  tree = _build_tree(keys)
550
559
  _print_tree(tree, prefix=" ")
560
+ return storage, keys
551
561
  except EnvironmentError as e:
552
562
  click.echo(f" ⚠️ {e}", err=True)
553
563
  except Exception as e:
554
564
  click.echo(f" ❌ {e}", err=True)
565
+ return None, []
555
566
 
556
567
 
557
568
  @main.command("buckets")
@@ -602,7 +613,33 @@ def buckets(env_file: tuple):
602
613
  sys.exit(0)
603
614
 
604
615
  selected = next(o for o in options if o["label"] == choice)
605
- _list_bucket(selected["kwargs"], selected["tier"], selected["bucket_name"])
616
+ storage, keys = _list_bucket(selected["kwargs"], selected["tier"], selected["bucket_name"])
617
+
618
+ if not storage or not keys:
619
+ return
620
+
621
+ file_choice = questionary.select(
622
+ "View a file (or press Esc to exit):",
623
+ choices=["(done)"] + keys,
624
+ ).ask()
625
+
626
+ if not file_choice or file_choice == "(done)":
627
+ return
628
+
629
+ click.echo(f"\n📄 {click.style(file_choice, fg='cyan')}\n")
630
+ try:
631
+ import tempfile
632
+ data = storage.get_bytes(file_choice)
633
+ suffix = Path(file_choice).suffix
634
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
635
+ tmp.write(data)
636
+ tmp_path = tmp.name
637
+ if not shutil.which("vd"):
638
+ click.secho("visidata not found. Install it with: uv pip install 'steve-cli[visidata]'", fg="yellow")
639
+ return
640
+ subprocess.call(["vd", tmp_path])
641
+ except Exception as e:
642
+ click.secho(f"❌ Could not open file: {e}", fg="red", err=True)
606
643
 
607
644
 
608
645
  if __name__ == '__main__':
@@ -26,6 +26,10 @@ def lineage_job(
26
26
 
27
27
  @functools.wraps(fn)
28
28
  def wrapper(*args: Any, **kwargs: Any) -> Any:
29
+ from dotenv import load_dotenv
30
+ load_dotenv(".env", override=False)
31
+ load_dotenv(".workspaces.env", override=False)
32
+
29
33
  session = make_session(
30
34
  namespace=namespace,
31
35
  job_name=job_name,
@@ -45,10 +49,11 @@ def lineage_job(
45
49
  result = fn(*args, get_storage=get_storage, **kwargs)
46
50
  except Exception as exc:
47
51
  session.fail(exc)
52
+ import sys
48
53
  from steve_cli.validation.port import DataQualityError
49
- if isinstance(exc, DataQualityError):
50
- import sys
51
- print(f"ERROR {exc}", file=sys.stderr)
54
+ if isinstance(exc, (DataQualityError, EnvironmentError)):
55
+ import click
56
+ click.secho(f"ERROR {exc}", fg="red", bold=True, err=True)
52
57
  sys.exit(1)
53
58
  raise
54
59
 
@@ -18,16 +18,21 @@ class LineageSession:
18
18
  _inputs: set[str] = field(default_factory=set)
19
19
  _outputs: set[str] = field(default_factory=set)
20
20
  _facets: Dict[str, Dict[str, Any]] = field(default_factory=dict)
21
+ _dataset_namespaces: Dict[str, str] = field(default_factory=dict)
21
22
 
22
- def record_read(self, dataset: str, facets: Dict[str, Any] | None = None) -> None:
23
+ def record_read(self, dataset: str, facets: Dict[str, Any] | None = None, namespace: str | None = None) -> None:
23
24
  self._inputs.add(dataset)
24
25
  if facets:
25
26
  self._facets.setdefault(dataset, {}).update(facets)
27
+ if namespace:
28
+ self._dataset_namespaces[dataset] = namespace
26
29
 
27
- def record_write(self, dataset: str, facets: Dict[str, Any] | None = None) -> None:
30
+ def record_write(self, dataset: str, facets: Dict[str, Any] | None = None, namespace: str | None = None) -> None:
28
31
  self._outputs.add(dataset)
29
32
  if facets:
30
33
  self._facets.setdefault(dataset, {}).update(facets)
34
+ if namespace:
35
+ self._dataset_namespaces[dataset] = namespace
31
36
 
32
37
  def attach_facets(self, dataset: str, facets: Dict[str, Any]) -> None:
33
38
  self._facets.setdefault(dataset, {}).update(facets)
@@ -42,8 +47,8 @@ class LineageSession:
42
47
  namespace=self.namespace,
43
48
  run_id=self.run_id,
44
49
  state=state,
45
- inputs=[DatasetRef(namespace=self.namespace, name=x, facets=self._facets.get(x, {})) for x in self._inputs],
46
- outputs=[DatasetRef(namespace=self.namespace, name=x, facets=self._facets.get(x, {})) for x in self._outputs],
50
+ inputs=[DatasetRef(namespace=self._dataset_namespaces.get(x, self.namespace), name=x, facets=self._facets.get(x, {})) for x in self._inputs],
51
+ outputs=[DatasetRef(namespace=self._dataset_namespaces.get(x, self.namespace), name=x, facets=self._facets.get(x, {})) for x in self._outputs],
47
52
  run_facets=run_facets or {},
48
53
  )
49
54
 
@@ -86,7 +91,11 @@ def make_session(
86
91
  kwargs = {"url": url} if prov == "openlineage" else {}
87
92
  try:
88
93
  port = LineageRegistry.create(prov, **kwargs)
89
- except Exception:
94
+ except Exception as exc:
95
+ import logging
96
+ logging.getLogger(__name__).warning(
97
+ "Lineage disabled: could not initialize provider %r: %s", prov, exc
98
+ )
90
99
  from .adapters.null import NullLineageAdapter
91
100
 
92
101
  port = NullLineageAdapter()
@@ -58,23 +58,30 @@ class LineageStorage:
58
58
  self.__resolved = self._storage_or_factory # type: ignore[assignment]
59
59
  return self.__resolved
60
60
 
61
+ @property
62
+ def _dataset_namespace(self) -> str | None:
63
+ s = self._storage
64
+ if hasattr(s, 'endpoint') and hasattr(s, 'bucket'):
65
+ return f"{s.endpoint.rstrip('/')}/{s.bucket}"
66
+ return None
67
+
61
68
  def put_file(self, local_path: str, path: str) -> None:
62
69
  self._storage.put_file(local_path, path)
63
- self._session.record_write(path)
70
+ self._session.record_write(path, namespace=self._dataset_namespace)
64
71
 
65
72
  def get_file(self, path: str, local_path: str) -> None:
66
73
  self._storage.get_file(path, local_path)
67
- self._session.record_read(path)
74
+ self._session.record_read(path, namespace=self._dataset_namespace)
68
75
 
69
76
  def put_bytes(self, data: bytes, path: str) -> None:
70
77
  self._storage.put_bytes(data, path)
71
78
  facets = _extract_facets(data, path)
72
- self._session.record_write(path, facets or None)
79
+ self._session.record_write(path, facets or None, namespace=self._dataset_namespace)
73
80
 
74
81
  def get_bytes(self, path: str) -> bytes:
75
82
  data = self._storage.get_bytes(path)
76
83
  facets = _extract_facets(data, path)
77
- self._session.record_read(path, facets or None)
84
+ self._session.record_read(path, facets or None, namespace=self._dataset_namespace)
78
85
  return data
79
86
 
80
87
  def read(self, path: str) -> DatasetHandle:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steve-cli
3
- Version: 0.3.8
3
+ Version: 0.3.11
4
4
  Summary: A simple CLI tool to run jobs from jobs.yaml with proper environment setup
5
5
  Author: Frank
6
6
  License: MIT
@@ -44,6 +44,8 @@ Requires-Dist: great-expectations>=0.18.0; extra == "great-expectations"
44
44
  Requires-Dist: pandas>=2.0.3; extra == "great-expectations"
45
45
  Provides-Extra: excel
46
46
  Requires-Dist: openpyxl>=3.1.0; extra == "excel"
47
+ Provides-Extra: visidata
48
+ Requires-Dist: visidata>=3.0; extra == "visidata"
47
49
  Provides-Extra: all
48
50
  Requires-Dist: openlineage-python>=1.0.0; extra == "all"
49
51
  Requires-Dist: pyarrow>=17.0.0; extra == "all"
@@ -52,6 +54,7 @@ Requires-Dist: validoopsie>=0.1.0; extra == "all"
52
54
  Requires-Dist: great-expectations>=0.18.0; extra == "all"
53
55
  Requires-Dist: pandas>=2.0.3; extra == "all"
54
56
  Requires-Dist: openpyxl>=3.1.0; extra == "all"
57
+ Requires-Dist: visidata>=3.0; extra == "all"
55
58
 
56
59
  # Steve CLI
57
60
 
@@ -12,6 +12,7 @@ validoopsie>=0.1.0
12
12
  great-expectations>=0.18.0
13
13
  pandas>=2.0.3
14
14
  openpyxl>=3.1.0
15
+ visidata>=3.0
15
16
 
16
17
  [dev]
17
18
  pytest>=7.0
@@ -43,3 +44,6 @@ pyarrow>=17.0.0
43
44
  [validoopsie]
44
45
  validoopsie>=0.1.0
45
46
  polars>=1.8.2
47
+
48
+ [visidata]
49
+ visidata>=3.0
File without changes
File without changes