sunstone-data-platform 1.3.0__tar.gz → 1.4.1__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.
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/PKG-INFO +7 -6
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/README.md +4 -4
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/pyproject.toml +5 -1
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/_version.py +2 -2
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/auth/stepup.py +4 -2
- sunstone_data_platform-1.4.1/src/sunstone_data/branch.py +66 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/__init__.py +1 -0
- sunstone_data_platform-1.4.1/src/sunstone_data/cli/commands/conceptmap.py +81 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/data_import.py +11 -3
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/data_publish.py +10 -1
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/refs.py +48 -1
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/show.py +9 -2
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/snapshots.py +10 -2
- sunstone_data_platform-1.4.1/src/sunstone_data/conceptmap.py +233 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/config.py +15 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/graph/client.py +157 -8
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/plugin.py +20 -3
- sunstone_data_platform-1.4.1/src/sunstone_data/push.py +134 -0
- sunstone_data_platform-1.4.1/src/sunstone_data/queries/conceptmap-mappings.rq +41 -0
- sunstone_data_platform-1.4.1/src/sunstone_data/sssom.py +267 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/assets/mirrors.py +1 -1
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/.gitignore +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/auth/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/auth/client_creds.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/auth/pkce.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/auth/store.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/catalog.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/_app.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/_errors.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/data_auth.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/data_info.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/ls.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/prov.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/sparql.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/main.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/discovery.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/env_commands.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/env_section.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/graph/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/graph/dataset.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/graph/terms.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/identifiers.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/import_/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/import_/fetcher.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/import_/owner.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/import_/parser.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/import_/pipeline.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/import_/storage.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/metadata/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/metadata/builder.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/metadata/models.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/metadata/provenance.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/metadata/provenance_rdf.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/publish.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/query.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/resolve.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/url.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/utils/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/utils/checksum.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/utils/encoding.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/utils/naming.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/versioning/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/versioning/schema_evolution.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/assets/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/assets/transforms.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/definitions.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/io_manager.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/resources/__init__.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/resources/iceberg.py +0 -0
- {sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_pipeline/resources/refs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sunstone-data-platform
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Sunstone Data Platform - Data lakehouse with semantic metadata
|
|
5
5
|
Author-email: Sunstone Institute <stig@sunstone.institute>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,8 +15,9 @@ Requires-Dist: pyiceberg[duckdb,gcsfs,s3fs]>=0.10.0
|
|
|
15
15
|
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
16
16
|
Requires-Dist: python-dotenv>=1.2.2
|
|
17
17
|
Requires-Dist: python-slugify>=8.0.0
|
|
18
|
+
Requires-Dist: rdflib>=7.6
|
|
18
19
|
Requires-Dist: rich>=14.0.0
|
|
19
|
-
Requires-Dist: sunstone-py>=1.
|
|
20
|
+
Requires-Dist: sunstone-py>=1.19.0
|
|
20
21
|
Requires-Dist: typer>=0.27.1
|
|
21
22
|
Provides-Extra: pipelines
|
|
22
23
|
Requires-Dist: dagster-docker>=0.29.0; extra == 'pipelines'
|
|
@@ -62,7 +63,7 @@ full data flow.
|
|
|
62
63
|
## Endpoints
|
|
63
64
|
|
|
64
65
|
- `/catalog` — DCAT JSON-LD view of the lakehouse, served by graph-server views
|
|
65
|
-
(see [
|
|
66
|
+
(see [DP-SPEC-13](https://worklode.dev.sunstoneinstitute.ai/DP-SPEC-13))
|
|
66
67
|
- `/ui/` — the data explorer, a 3D view of the data graph, served by
|
|
67
68
|
graph-server (see [docs/data-explorer.md](docs/data-explorer.md))
|
|
68
69
|
|
|
@@ -264,8 +265,8 @@ src/sunstone_data/
|
|
|
264
265
|
## Documentation
|
|
265
266
|
|
|
266
267
|
- [docs/README.md](docs/README.md) - Index of all documentation
|
|
267
|
-
- [docs/architecture.md](docs/architecture.md) - System overview and
|
|
268
|
-
- [docs/
|
|
268
|
+
- [docs/architecture.md](docs/architecture.md) - System overview, data flow, and an index of the specs in Worklode. The Python client and CLI are in [DP-SPEC-1](https://worklode.dev.sunstoneinstitute.ai/DP-SPEC-1).
|
|
269
|
+
- [docs/releasing.md](docs/releasing.md) - How to release the server and the client
|
|
269
270
|
|
|
270
271
|
## Resetting the Stack
|
|
271
272
|
|
|
@@ -285,7 +286,7 @@ MIT
|
|
|
285
286
|
`graph-server` requires a Keycloak bearer token on every route except
|
|
286
287
|
`/healthz`/`/readyz`. Access is role-based: the client roles in your token
|
|
287
288
|
determine which environment you can use. See
|
|
288
|
-
|
|
289
|
+
[DP-SPEC-14](https://worklode.dev.sunstoneinstitute.ai/DP-SPEC-14).
|
|
289
290
|
|
|
290
291
|
No hostnames or client ids ship in this package. Each environment's
|
|
291
292
|
`base_iri` comes from config: Sunstone-managed machines get
|
|
@@ -35,7 +35,7 @@ full data flow.
|
|
|
35
35
|
## Endpoints
|
|
36
36
|
|
|
37
37
|
- `/catalog` — DCAT JSON-LD view of the lakehouse, served by graph-server views
|
|
38
|
-
(see [
|
|
38
|
+
(see [DP-SPEC-13](https://worklode.dev.sunstoneinstitute.ai/DP-SPEC-13))
|
|
39
39
|
- `/ui/` — the data explorer, a 3D view of the data graph, served by
|
|
40
40
|
graph-server (see [docs/data-explorer.md](docs/data-explorer.md))
|
|
41
41
|
|
|
@@ -237,8 +237,8 @@ src/sunstone_data/
|
|
|
237
237
|
## Documentation
|
|
238
238
|
|
|
239
239
|
- [docs/README.md](docs/README.md) - Index of all documentation
|
|
240
|
-
- [docs/architecture.md](docs/architecture.md) - System overview and
|
|
241
|
-
- [docs/
|
|
240
|
+
- [docs/architecture.md](docs/architecture.md) - System overview, data flow, and an index of the specs in Worklode. The Python client and CLI are in [DP-SPEC-1](https://worklode.dev.sunstoneinstitute.ai/DP-SPEC-1).
|
|
241
|
+
- [docs/releasing.md](docs/releasing.md) - How to release the server and the client
|
|
242
242
|
|
|
243
243
|
## Resetting the Stack
|
|
244
244
|
|
|
@@ -258,7 +258,7 @@ MIT
|
|
|
258
258
|
`graph-server` requires a Keycloak bearer token on every route except
|
|
259
259
|
`/healthz`/`/readyz`. Access is role-based: the client roles in your token
|
|
260
260
|
determine which environment you can use. See
|
|
261
|
-
|
|
261
|
+
[DP-SPEC-14](https://worklode.dev.sunstoneinstitute.ai/DP-SPEC-14).
|
|
262
262
|
|
|
263
263
|
No hostnames or client ids ship in this package. Each environment's
|
|
264
264
|
`base_iri` comes from config: Sunstone-managed machines get
|
|
@@ -13,7 +13,7 @@ authors = [
|
|
|
13
13
|
{ name = "Sunstone Institute", email = "stig@sunstone.institute" }
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"sunstone-py>=1.
|
|
16
|
+
"sunstone-py>=1.19.0",
|
|
17
17
|
"pyiceberg[s3fs,gcsfs,duckdb]>=0.10.0",
|
|
18
18
|
"duckdb>=1.5.3",
|
|
19
19
|
"python-dotenv>=1.2.2",
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
"python-dateutil>=2.9.0.post0",
|
|
27
27
|
"publicsuffixlist>=1.0.2.20261003",
|
|
28
28
|
"keyring>=25",
|
|
29
|
+
"rdflib>=7.6",
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
[project.entry-points."sunstone.plugins"]
|
|
@@ -41,6 +42,9 @@ pipelines = [
|
|
|
41
42
|
|
|
42
43
|
[tool.hatch.version]
|
|
43
44
|
source = "vcs"
|
|
45
|
+
# Only `v*` tags are client versions. Without --match, setuptools-scm takes the
|
|
46
|
+
# nearest tag of any kind and strips a `server-` prefix (DP-REQ-391).
|
|
47
|
+
raw-options = { git_describe_command = "git describe --dirty --tags --long --match v[0-9]*" }
|
|
44
48
|
|
|
45
49
|
[tool.hatch.build.hooks.vcs]
|
|
46
50
|
version-file = "src/sunstone_data/_version.py"
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.4.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 4, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
{sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/auth/stepup.py
RENAMED
|
@@ -83,10 +83,12 @@ def step_up(
|
|
|
83
83
|
"""
|
|
84
84
|
settings = get_settings()
|
|
85
85
|
if os.environ.get("SUNSTONE_ACCESS_TOKEN", "").strip() or (settings.client_id and settings.client_secret):
|
|
86
|
-
raise StepUpRequired("writes to
|
|
86
|
+
raise StepUpRequired("writes to a protected ref need a passkey sign-in; a service credential cannot step up")
|
|
87
87
|
session = store.read_session(env_name)
|
|
88
88
|
if session is None:
|
|
89
|
-
raise StepUpRequired(
|
|
89
|
+
raise StepUpRequired(
|
|
90
|
+
f"writes to a protected ref need a passkey sign-in; run `sunstone auth login` for {env_name!r}"
|
|
91
|
+
)
|
|
90
92
|
issuer = session.issuer or discovered_issuer(env_name, settings.graph_url)
|
|
91
93
|
authorization_endpoint = discover_authorization_endpoint(issuer, client)
|
|
92
94
|
token_endpoint = discover_token_endpoint(issuer, client)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Default data branch: the current git branch (or CI ref), normalized (DP-SPEC-12 section 12)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
_MAX_LENGTH = 128
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NoDataBranchError(ValueError):
|
|
15
|
+
"""No data branch could be derived; the caller must pass ``--branch``."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalize_branch_name(name: str) -> str:
|
|
19
|
+
"""Turn a git branch name into a valid data branch name.
|
|
20
|
+
|
|
21
|
+
In order: characters outside ``[A-Za-z0-9_.-]`` become ``-``, runs of
|
|
22
|
+
``-`` collapse to one, a name not starting with a letter or ``_`` gets a
|
|
23
|
+
``_`` prefix, and the result is cut to 128 characters.
|
|
24
|
+
"""
|
|
25
|
+
name = re.sub(r"[^A-Za-z0-9_.-]", "-", name)
|
|
26
|
+
name = re.sub(r"-{2,}", "-", name)
|
|
27
|
+
if not re.match(r"[A-Za-z_]", name):
|
|
28
|
+
name = "_" + name
|
|
29
|
+
return name[:_MAX_LENGTH]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _git_branch(cwd: Path | None) -> str | None:
|
|
33
|
+
"""The checked-out git branch, or None (not a checkout, git missing, detached HEAD)."""
|
|
34
|
+
try:
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
["git", "symbolic-ref", "--short", "-q", "HEAD"],
|
|
37
|
+
cwd=cwd,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
timeout=10,
|
|
41
|
+
check=False,
|
|
42
|
+
)
|
|
43
|
+
except (OSError, subprocess.SubprocessError):
|
|
44
|
+
return None
|
|
45
|
+
name = result.stdout.strip()
|
|
46
|
+
return name if result.returncode == 0 and name else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def current_data_branch(cwd: Path | None = None, env: Mapping[str, str] | None = None) -> str:
|
|
50
|
+
"""Return the data branch for ``cwd``, normalized.
|
|
51
|
+
|
|
52
|
+
Order: the checked-out git branch (also on a branch with no commits yet),
|
|
53
|
+
then ``GITHUB_HEAD_REF``, then ``GITHUB_REF_NAME``. CI checks out pull
|
|
54
|
+
requests on a detached HEAD, which is why the variables come second.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
NoDataBranchError: None of these gives a branch. Never falls back to ``main``.
|
|
58
|
+
"""
|
|
59
|
+
env = os.environ if env is None else env
|
|
60
|
+
name = _git_branch(cwd) or env.get("GITHUB_HEAD_REF") or env.get("GITHUB_REF_NAME")
|
|
61
|
+
if not name:
|
|
62
|
+
raise NoDataBranchError(
|
|
63
|
+
"Cannot tell which data branch to use: not on a git branch and GITHUB_HEAD_REF / "
|
|
64
|
+
"GITHUB_REF_NAME are unset. Pass --branch."
|
|
65
|
+
)
|
|
66
|
+
return normalize_branch_name(name)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""``sunstone data conceptmap create|update|sssom``: push a map-concepts scope directory, read its mappings (DP-REQ-323)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import asdict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from sunstone_data.cli._app import console, data_app
|
|
13
|
+
from sunstone_data.cli._errors import cli_errors
|
|
14
|
+
|
|
15
|
+
conceptmap_app = typer.Typer(help="Push concept maps from map-concepts scope directories", no_args_is_help=True)
|
|
16
|
+
data_app.add_typer(conceptmap_app, name="conceptmap")
|
|
17
|
+
|
|
18
|
+
_SCOPE_DIR = typer.Argument(
|
|
19
|
+
..., help="Scope directory written by map-concepts (reads scheme/*.ttl and scheme/mappings.sssom.tsv)"
|
|
20
|
+
)
|
|
21
|
+
# Concept maps are published content, so the default is main, not the git
|
|
22
|
+
# branch of the directory the command runs in (DP-REQ-30).
|
|
23
|
+
_BRANCH = typer.Option("main", "--branch", "-b", help="Branch to write")
|
|
24
|
+
_BRANCH_READ = typer.Option("main", "--branch", "-b", help="Branch to read")
|
|
25
|
+
_JSON = typer.Option(False, "--json", help="Output as JSON")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _report(result, json_output: bool) -> None:
|
|
29
|
+
from urllib.parse import quote
|
|
30
|
+
|
|
31
|
+
from sunstone_data.config import get_settings
|
|
32
|
+
|
|
33
|
+
link = f"{get_settings().graph_url}/ui/#visualise?graph={quote(result.graph_iri, safe='')}"
|
|
34
|
+
if json_output:
|
|
35
|
+
print(json.dumps({**asdict(result), "url": link}))
|
|
36
|
+
return
|
|
37
|
+
console.print(
|
|
38
|
+
f"Concept map [cyan]{result.conceptmap}[/cyan]: {result.graph_iri} on {result.branch}, "
|
|
39
|
+
f"{result.triples} triples (ETag {result.etag})"
|
|
40
|
+
)
|
|
41
|
+
console.print(link)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@conceptmap_app.command("create")
|
|
45
|
+
def create_cmd(scope_dir: Path = _SCOPE_DIR, branch: str = _BRANCH, json_output: bool = _JSON) -> None:
|
|
46
|
+
"""Allocate a new concept map and push the scope directory to it."""
|
|
47
|
+
with cli_errors(json_output):
|
|
48
|
+
from sunstone_data import conceptmap
|
|
49
|
+
|
|
50
|
+
_report(conceptmap.create(scope_dir, branch), json_output)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@conceptmap_app.command("update")
|
|
54
|
+
def update_cmd(
|
|
55
|
+
n: int = typer.Argument(..., help="Concept map id, the <n> in /conceptmap/<n>"),
|
|
56
|
+
scope_dir: Path = _SCOPE_DIR,
|
|
57
|
+
branch: str = _BRANCH,
|
|
58
|
+
json_output: bool = _JSON,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Replace an existing concept map with the scope directory."""
|
|
61
|
+
with cli_errors(json_output):
|
|
62
|
+
from sunstone_data import conceptmap
|
|
63
|
+
|
|
64
|
+
_report(conceptmap.update(n, scope_dir, branch), json_output)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@conceptmap_app.command("sssom")
|
|
68
|
+
def sssom_cmd(
|
|
69
|
+
n: int = typer.Argument(..., help="Concept map id, the <n> in /conceptmap/<n>"),
|
|
70
|
+
branch: str = _BRANCH_READ,
|
|
71
|
+
output: Path | None = typer.Option(None, "--output", "-o", help="File to write (default: stdout)"),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Write a concept map's mappings as an SSSOM TSV file."""
|
|
74
|
+
with cli_errors(False):
|
|
75
|
+
from sunstone_data import conceptmap
|
|
76
|
+
|
|
77
|
+
text = conceptmap.sssom(n, branch)
|
|
78
|
+
if output is None:
|
|
79
|
+
sys.stdout.write(text)
|
|
80
|
+
else:
|
|
81
|
+
output.write_text(text, encoding="utf-8")
|
|
@@ -49,8 +49,7 @@ def _format_result(result, as_json: bool = False, branch: str = "main") -> None:
|
|
|
49
49
|
console.print(f" Snapshot: {result.snapshot_id}")
|
|
50
50
|
console.print(f" Dataset: [cyan]{iri}[/cyan]")
|
|
51
51
|
console.print(f" Activity: [cyan]{activity}[/cyan]")
|
|
52
|
-
|
|
53
|
-
console.print(f" Branch: [cyan]{branch}[/cyan]")
|
|
52
|
+
console.print(f" Branch: [cyan]{branch}[/cyan]")
|
|
54
53
|
|
|
55
54
|
|
|
56
55
|
@data_app.command("import")
|
|
@@ -71,7 +70,12 @@ def import_cmd(
|
|
|
71
70
|
help="Publisher organization path, e.g. un/who. Overrides the source-URL rules file. Does not set the namespace.",
|
|
72
71
|
),
|
|
73
72
|
tag: list[str] | None = typer.Option(None, "--tag", "-t", help="Add tags (key:value)"),
|
|
74
|
-
branch: str = typer.Option(
|
|
73
|
+
branch: str | None = typer.Option(
|
|
74
|
+
None,
|
|
75
|
+
"--branch",
|
|
76
|
+
"-b",
|
|
77
|
+
help="Branch to import to (default: the normalized git branch, else the CI ref; fails if neither)",
|
|
78
|
+
),
|
|
75
79
|
force_schema: bool = typer.Option(False, "--force-schema", help="Allow unsafe schema changes"),
|
|
76
80
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
77
81
|
) -> None:
|
|
@@ -86,11 +90,15 @@ def import_cmd(
|
|
|
86
90
|
TransferSpeedColumn,
|
|
87
91
|
)
|
|
88
92
|
|
|
93
|
+
from sunstone_data.branch import current_data_branch
|
|
89
94
|
from sunstone_data.import_ import run_import
|
|
90
95
|
|
|
91
96
|
is_url = source.startswith(("http://", "https://"))
|
|
92
97
|
|
|
93
98
|
try:
|
|
99
|
+
if branch is None:
|
|
100
|
+
branch = current_data_branch()
|
|
101
|
+
|
|
94
102
|
if sys.stdout.isatty() and not json_output:
|
|
95
103
|
with Progress(
|
|
96
104
|
SpinnerColumn(),
|
|
@@ -10,7 +10,12 @@ from sunstone_data.cli._app import console, data_app
|
|
|
10
10
|
@data_app.command("publish")
|
|
11
11
|
def publish_cmd(
|
|
12
12
|
table: str = typer.Argument(..., help="Table identifier (namespace.table)"),
|
|
13
|
-
branch: str = typer.Option(
|
|
13
|
+
branch: str | None = typer.Option(
|
|
14
|
+
None,
|
|
15
|
+
"--branch",
|
|
16
|
+
"-b",
|
|
17
|
+
help="Branch to publish on (default: the normalized git branch, else the CI ref; fails if neither)",
|
|
18
|
+
),
|
|
14
19
|
version: str | None = typer.Option(
|
|
15
20
|
None, "--version", help="Publish the table's current snapshot under this version label, e.g. v1.0"
|
|
16
21
|
),
|
|
@@ -27,6 +32,7 @@ def publish_cmd(
|
|
|
27
32
|
|
|
28
33
|
from sunstone.lineage import Metadata
|
|
29
34
|
|
|
35
|
+
from sunstone_data.branch import current_data_branch
|
|
30
36
|
from sunstone_data.catalog import get_catalog
|
|
31
37
|
from sunstone_data.graph.dataset import DatasetDescription, DatasetVersion
|
|
32
38
|
from sunstone_data.publish import publish
|
|
@@ -34,6 +40,9 @@ def publish_cmd(
|
|
|
34
40
|
from sunstone_data.resolve import dataset_iri
|
|
35
41
|
|
|
36
42
|
try:
|
|
43
|
+
if branch is None:
|
|
44
|
+
branch = current_data_branch()
|
|
45
|
+
|
|
37
46
|
ref = TableRef.parse(table)
|
|
38
47
|
identifier = f"{ref.namespace}.{ref.table}"
|
|
39
48
|
versions: tuple[DatasetVersion, ...] = ()
|
{sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/refs.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""graph-server refs: ``branches``, ``branch create|delete``, ``tags``,
|
|
1
|
+
"""graph-server refs: ``branches``, ``branch create|delete|merge``, ``tags``,
|
|
2
2
|
``tag create|delete`` (spec 180 §8).
|
|
3
3
|
|
|
4
4
|
Branch and tag names go into a URL path, so they are validated here before
|
|
@@ -93,6 +93,53 @@ def branch_delete_cmd(
|
|
|
93
93
|
console.print(f"[green]Deleted branch[/green] {name}")
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
@branch_app.command("merge")
|
|
97
|
+
def branch_merge_cmd(
|
|
98
|
+
name: str = typer.Argument(..., help="Name of the branch to merge into main"),
|
|
99
|
+
strategy: str = typer.Option(
|
|
100
|
+
None,
|
|
101
|
+
"--strategy",
|
|
102
|
+
help="How to settle conflicts: report (default), branch-wins or main-wins",
|
|
103
|
+
),
|
|
104
|
+
take_branch: list[str] = typer.Option(
|
|
105
|
+
[], "--take-branch", help="Graph IRI whose conflict takes the branch side (repeatable)"
|
|
106
|
+
),
|
|
107
|
+
take_main: list[str] = typer.Option(
|
|
108
|
+
[], "--take-main", help="Graph IRI whose conflict keeps main's side (repeatable)"
|
|
109
|
+
),
|
|
110
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Merge a branch into main. Conflicts are reported unless resolved.
|
|
113
|
+
|
|
114
|
+
Exits 2 when conflicts remain, so CI knows to keep the branch.
|
|
115
|
+
"""
|
|
116
|
+
with cli_errors(json_output):
|
|
117
|
+
from sunstone_data.identifiers import validate_ref_name
|
|
118
|
+
|
|
119
|
+
validate_ref_name(name, "branch")
|
|
120
|
+
if strategy is not None and strategy not in ("report", "branch-wins", "main-wins"):
|
|
121
|
+
raise ValueError(f"--strategy must be report, branch-wins or main-wins, not {strategy!r}")
|
|
122
|
+
resolve = [{"graph": g, "take": "branch"} for g in take_branch]
|
|
123
|
+
resolve += [{"graph": g, "take": "main"} for g in take_main]
|
|
124
|
+
result = _client().merge_branch(name, strategy, resolve or None)
|
|
125
|
+
|
|
126
|
+
merged, conflicts = result.get("merged", []), result.get("conflicts", [])
|
|
127
|
+
if json_output:
|
|
128
|
+
print(json.dumps(result, indent=2))
|
|
129
|
+
if conflicts:
|
|
130
|
+
raise typer.Exit(2)
|
|
131
|
+
return
|
|
132
|
+
color = "yellow" if conflicts else "green"
|
|
133
|
+
console.print(f"[{color}]Merged[/{color}] {name} into main: {len(merged)} merged, {len(conflicts)} in conflict")
|
|
134
|
+
for e in merged:
|
|
135
|
+
note = f" (took {e['resolution']})" if e.get("resolution") else ""
|
|
136
|
+
console.print(f" merged {e.get('table') or e['graph']}{note}")
|
|
137
|
+
for e in conflicts:
|
|
138
|
+
console.print(f" [red]conflict[/red] {e.get('table') or e['graph']}")
|
|
139
|
+
if conflicts:
|
|
140
|
+
raise typer.Exit(2)
|
|
141
|
+
|
|
142
|
+
|
|
96
143
|
@data_app.command("tags")
|
|
97
144
|
def tags_cmd(
|
|
98
145
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
{sunstone_data_platform-1.3.0 → sunstone_data_platform-1.4.1}/src/sunstone_data/cli/commands/show.py
RENAMED
|
@@ -20,7 +20,12 @@ FORMATS = ("table", "turtle", "jsonld")
|
|
|
20
20
|
def show_cmd(
|
|
21
21
|
table: str = typer.Argument(..., help="Table reference (namespace.table[@branch])"),
|
|
22
22
|
output_format: str = typer.Option("table", "--format", "-f", help=f"One of: {', '.join(FORMATS)}"),
|
|
23
|
-
branch: str = typer.Option(
|
|
23
|
+
branch: str | None = typer.Option(
|
|
24
|
+
None,
|
|
25
|
+
"--branch",
|
|
26
|
+
"-b",
|
|
27
|
+
help="Branch to read the graph from (default: the normalized git branch, else the CI ref; fails if neither)",
|
|
28
|
+
),
|
|
24
29
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON-LD (same as --format jsonld)"),
|
|
25
30
|
) -> None:
|
|
26
31
|
"""Show a dataset's metadata graph.
|
|
@@ -35,6 +40,7 @@ def show_cmd(
|
|
|
35
40
|
if chosen not in FORMATS:
|
|
36
41
|
raise ValueError(f"Unknown format {output_format!r}. Use one of: {', '.join(FORMATS)}")
|
|
37
42
|
|
|
43
|
+
from sunstone_data.branch import current_data_branch
|
|
38
44
|
from sunstone_data.graph.client import N_TRIPLES, get_graph_client
|
|
39
45
|
from sunstone_data.query import TableRef
|
|
40
46
|
from sunstone_data.resolve import dataset_iri
|
|
@@ -44,7 +50,7 @@ def show_cmd(
|
|
|
44
50
|
client = get_graph_client()
|
|
45
51
|
# A ref after `@` names a graph-server branch here: the graph is
|
|
46
52
|
# per-branch, and versions/snapshots are inside it, not selectors on it.
|
|
47
|
-
source_branch = ref.version_ref or branch
|
|
53
|
+
source_branch = ref.version_ref or branch or current_data_branch()
|
|
48
54
|
|
|
49
55
|
if chosen == "turtle":
|
|
50
56
|
print(client.get_graph(source_branch, iri))
|
|
@@ -58,6 +64,7 @@ def show_cmd(
|
|
|
58
64
|
print(json.dumps(to_jsonld(graph), indent=2))
|
|
59
65
|
return
|
|
60
66
|
|
|
67
|
+
console.print(f"Branch: [cyan]{source_branch}[/cyan]")
|
|
61
68
|
_print_table(graph, iri)
|
|
62
69
|
|
|
63
70
|
|
|
@@ -16,7 +16,12 @@ from sunstone_data.cli._errors import cli_errors
|
|
|
16
16
|
@data_app.command("history")
|
|
17
17
|
def history_cmd(
|
|
18
18
|
table: str = typer.Argument(..., help="Table reference (namespace.table[@branch])"),
|
|
19
|
-
branch: str = typer.Option(
|
|
19
|
+
branch: str | None = typer.Option(
|
|
20
|
+
None,
|
|
21
|
+
"--branch",
|
|
22
|
+
"-b",
|
|
23
|
+
help="Catalog branch to read (default: the normalized git branch, else the CI ref; fails if neither)",
|
|
24
|
+
),
|
|
20
25
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
21
26
|
) -> None:
|
|
22
27
|
"""Show a table's Iceberg snapshot log, oldest snapshot first.
|
|
@@ -28,11 +33,13 @@ def history_cmd(
|
|
|
28
33
|
with cli_errors(json_output):
|
|
29
34
|
from datetime import datetime, timezone
|
|
30
35
|
|
|
36
|
+
from sunstone_data.branch import current_data_branch
|
|
31
37
|
from sunstone_data.catalog import get_catalog
|
|
32
38
|
from sunstone_data.query import TableRef
|
|
33
39
|
|
|
34
40
|
ref = TableRef.parse(table)
|
|
35
|
-
|
|
41
|
+
source_branch = ref.version_ref or branch or current_data_branch()
|
|
42
|
+
iceberg_table = get_catalog(source_branch).load_table(f"{ref.namespace}.{ref.table}")
|
|
36
43
|
|
|
37
44
|
rows = []
|
|
38
45
|
for entry in iceberg_table.history():
|
|
@@ -54,6 +61,7 @@ def history_cmd(
|
|
|
54
61
|
print(json.dumps(rows, indent=2))
|
|
55
62
|
return
|
|
56
63
|
|
|
64
|
+
console.print(f"Branch: [cyan]{source_branch}[/cyan]")
|
|
57
65
|
if not rows:
|
|
58
66
|
console.print("[yellow]No snapshots found[/yellow]")
|
|
59
67
|
return
|