sunstone-data-platform 1.2.0__tar.gz → 1.3.0__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.2.0 → sunstone_data_platform-1.3.0}/.gitignore +1 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/PKG-INFO +7 -4
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/README.md +5 -3
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/pyproject.toml +1 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/_version.py +2 -2
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/__init__.py +3 -2
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/pkce.py +1 -1
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/stepup.py +1 -1
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/store.py +65 -8
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/_app.py +4 -1
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/data_auth.py +9 -9
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/main.py +4 -4
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/config.py +9 -2
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/env_section.py +7 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/client.py +2 -2
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/plugin.py +4 -4
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/client_creds.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/catalog.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/_errors.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/data_import.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/data_info.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/data_publish.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/ls.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/prov.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/refs.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/show.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/snapshots.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/sparql.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/discovery.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/env_commands.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/dataset.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/terms.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/identifiers.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/fetcher.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/owner.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/parser.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/pipeline.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/storage.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/builder.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/models.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/provenance.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/provenance_rdf.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/publish.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/query.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/resolve.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/url.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/checksum.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/encoding.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/naming.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/versioning/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/versioning/schema_evolution.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/assets/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/assets/mirrors.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/assets/transforms.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/definitions.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/io_manager.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/resources/__init__.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/resources/iceberg.py +0 -0
- {sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/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.3.0
|
|
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
|
|
@@ -9,6 +9,7 @@ Requires-Dist: charset-normalizer>=3.4.0
|
|
|
9
9
|
Requires-Dist: duckdb>=1.5.3
|
|
10
10
|
Requires-Dist: fastexcel>=0.20.2
|
|
11
11
|
Requires-Dist: httpx>=0.27.0
|
|
12
|
+
Requires-Dist: keyring>=25
|
|
12
13
|
Requires-Dist: publicsuffixlist>=1.0.2.20261003
|
|
13
14
|
Requires-Dist: pyiceberg[duckdb,gcsfs,s3fs]>=0.10.0
|
|
14
15
|
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
@@ -199,7 +200,7 @@ uv run pytest tests/unit/test_query.py -v
|
|
|
199
200
|
|
|
200
201
|
| Command | Description |
|
|
201
202
|
|---------|-------------|
|
|
202
|
-
| `sunstone
|
|
203
|
+
| `sunstone auth login`, `logout`, `whoami` | Sign in to graph-server (Keycloak) |
|
|
203
204
|
| `sunstone data ls [namespace]` | List namespaces and tables |
|
|
204
205
|
| `sunstone data show <table>` | Dataset metadata from the graph (table, Turtle or JSON-LD) |
|
|
205
206
|
| `sunstone data info [table]` | Iceberg table properties for one or all datasets |
|
|
@@ -297,11 +298,13 @@ graph-server publishes the OIDC issuer and public client id at
|
|
|
297
298
|
|
|
298
299
|
```bash
|
|
299
300
|
sunstone env init # cache discovery for configured environments
|
|
300
|
-
sunstone
|
|
301
|
+
sunstone auth login # browser sign-in (authorization code + PKCE)
|
|
301
302
|
```
|
|
302
303
|
|
|
303
304
|
A browser window opens for Keycloak SSO; the refresh token renews silently
|
|
304
|
-
until the SSO session expires.
|
|
305
|
+
until the SSO session expires. The refresh token is stored in the OS keyring
|
|
306
|
+
(macOS Keychain, Windows Credential Locker, Linux Secret Service). Without a
|
|
307
|
+
usable keyring it falls back to a mode-0600 file under `~/.cache/sunstone/`.
|
|
305
308
|
|
|
306
309
|
### Services (CI, automation)
|
|
307
310
|
|
|
@@ -173,7 +173,7 @@ uv run pytest tests/unit/test_query.py -v
|
|
|
173
173
|
|
|
174
174
|
| Command | Description |
|
|
175
175
|
|---------|-------------|
|
|
176
|
-
| `sunstone
|
|
176
|
+
| `sunstone auth login`, `logout`, `whoami` | Sign in to graph-server (Keycloak) |
|
|
177
177
|
| `sunstone data ls [namespace]` | List namespaces and tables |
|
|
178
178
|
| `sunstone data show <table>` | Dataset metadata from the graph (table, Turtle or JSON-LD) |
|
|
179
179
|
| `sunstone data info [table]` | Iceberg table properties for one or all datasets |
|
|
@@ -271,11 +271,13 @@ graph-server publishes the OIDC issuer and public client id at
|
|
|
271
271
|
|
|
272
272
|
```bash
|
|
273
273
|
sunstone env init # cache discovery for configured environments
|
|
274
|
-
sunstone
|
|
274
|
+
sunstone auth login # browser sign-in (authorization code + PKCE)
|
|
275
275
|
```
|
|
276
276
|
|
|
277
277
|
A browser window opens for Keycloak SSO; the refresh token renews silently
|
|
278
|
-
until the SSO session expires.
|
|
278
|
+
until the SSO session expires. The refresh token is stored in the OS keyring
|
|
279
|
+
(macOS Keychain, Windows Credential Locker, Linux Secret Service). Without a
|
|
280
|
+
usable keyring it falls back to a mode-0600 file under `~/.cache/sunstone/`.
|
|
279
281
|
|
|
280
282
|
### Services (CI, automation)
|
|
281
283
|
|
|
@@ -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.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/__init__.py
RENAMED
|
@@ -12,8 +12,9 @@ health, the UI and public views. Two ways to get one:
|
|
|
12
12
|
environment credential, so CI is never silently authenticated as whoever
|
|
13
13
|
last logged in on that machine.
|
|
14
14
|
|
|
15
|
-
Access tokens live in memory only. The refresh token is the sole
|
|
16
|
-
|
|
15
|
+
Access tokens live in memory only. The refresh token is the sole credential
|
|
16
|
+
kept between runs: in the OS keyring, or a mode-0600 file when no keyring is
|
|
17
|
+
usable.
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
from sunstone_data.auth.client_creds import (
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/pkce.py
RENAMED
|
@@ -13,7 +13,7 @@ wildcard for ports, so the port cannot be ephemeral. The port is bound
|
|
|
13
13
|
*before* the authorization URL is built, because the ``redirect_uri`` must
|
|
14
14
|
name it.
|
|
15
15
|
|
|
16
|
-
This is the flow behind ``sunstone
|
|
16
|
+
This is the flow behind ``sunstone auth login`` (spec 180 §4). The public
|
|
17
17
|
client it signs in as is the one graph-server publishes as ``clientId`` in
|
|
18
18
|
``/ui/config.json``, the same one the graph explorer UI uses.
|
|
19
19
|
"""
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/stepup.py
RENAMED
|
@@ -86,7 +86,7 @@ def step_up(
|
|
|
86
86
|
raise StepUpRequired("writes to main 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(f"writes to main need a passkey sign-in; run `sunstone
|
|
89
|
+
raise StepUpRequired(f"writes to main need a passkey sign-in; run `sunstone auth login` for {env_name!r}")
|
|
90
90
|
issuer = session.issuer or discovered_issuer(env_name, settings.graph_url)
|
|
91
91
|
authorization_endpoint = discover_authorization_endpoint(issuer, client)
|
|
92
92
|
token_endpoint = discover_token_endpoint(issuer, client)
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/store.py
RENAMED
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
"""Per-environment cache for a human login session.
|
|
2
2
|
|
|
3
|
-
Holds the OAuth2 refresh token from ``sunstone
|
|
3
|
+
Holds the OAuth2 refresh token from ``sunstone auth login`` so a user logs in
|
|
4
4
|
once per environment. Access tokens are never stored here — they are minted on
|
|
5
5
|
demand and kept in memory only.
|
|
6
6
|
|
|
7
|
-
The
|
|
8
|
-
|
|
7
|
+
The refresh token goes in the OS keyring (macOS Keychain, Windows Credential
|
|
8
|
+
Locker, Secret Service on Linux) under service ``sunstone-data-platform`` and
|
|
9
|
+
the environment name. The rest of the session (issuer, client id, time) goes
|
|
10
|
+
in a file next to the discovery cache, one level down so the two never
|
|
11
|
+
collide::
|
|
9
12
|
|
|
10
13
|
$XDG_CACHE_HOME/sunstone/data-platform/auth/{env_name}.json
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
When no keyring is usable (e.g. a headless Linux box), the refresh token is
|
|
16
|
+
written into that file instead. The file is always written atomically and
|
|
17
|
+
chmod'ed ``0600``, so no other user on the machine can read it.
|
|
14
18
|
"""
|
|
15
19
|
|
|
16
20
|
from __future__ import annotations
|
|
17
21
|
|
|
18
22
|
import json
|
|
23
|
+
import logging
|
|
19
24
|
import os
|
|
20
25
|
import tempfile
|
|
21
26
|
import time
|
|
22
27
|
from dataclasses import asdict, dataclass, field, fields
|
|
23
28
|
from pathlib import Path
|
|
24
29
|
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
25
32
|
#: Mode a session file must end up with — owner read/write only.
|
|
26
33
|
SESSION_FILE_MODE = 0o600
|
|
27
34
|
|
|
35
|
+
#: Keyring service name; the keyring username is the environment name.
|
|
36
|
+
KEYRING_SERVICE = "sunstone-data-platform"
|
|
37
|
+
|
|
28
38
|
|
|
29
39
|
@dataclass(frozen=True)
|
|
30
40
|
class Session:
|
|
@@ -53,18 +63,56 @@ def session_path(env_name: str) -> Path:
|
|
|
53
63
|
return _auth_dir() / f"{env_name}.json"
|
|
54
64
|
|
|
55
65
|
|
|
66
|
+
def _keyring_set(env_name: str, token: str) -> bool:
|
|
67
|
+
"""Store ``token`` in the OS keyring. Returns False if no keyring is usable."""
|
|
68
|
+
try:
|
|
69
|
+
import keyring
|
|
70
|
+
|
|
71
|
+
keyring.set_password(KEYRING_SERVICE, env_name, token)
|
|
72
|
+
except Exception:
|
|
73
|
+
logger.debug("OS keyring not usable", exc_info=True)
|
|
74
|
+
return False
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _keyring_get(env_name: str) -> str | None:
|
|
79
|
+
try:
|
|
80
|
+
import keyring
|
|
81
|
+
|
|
82
|
+
return keyring.get_password(KEYRING_SERVICE, env_name)
|
|
83
|
+
except Exception:
|
|
84
|
+
logger.debug("OS keyring not usable", exc_info=True)
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _keyring_delete(env_name: str) -> None:
|
|
89
|
+
try:
|
|
90
|
+
import keyring
|
|
91
|
+
|
|
92
|
+
keyring.delete_password(KEYRING_SERVICE, env_name)
|
|
93
|
+
except Exception:
|
|
94
|
+
# Missing entry or no usable keyring: nothing to delete either way.
|
|
95
|
+
logger.debug("No keyring entry deleted", exc_info=True)
|
|
96
|
+
|
|
97
|
+
|
|
56
98
|
def write_session(env_name: str, session: Session) -> Path:
|
|
57
99
|
"""Persist a login session, atomically and mode 0600.
|
|
58
100
|
|
|
59
|
-
|
|
101
|
+
The refresh token goes in the OS keyring when one is usable, else in the
|
|
102
|
+
session file. Returns the path written.
|
|
60
103
|
"""
|
|
61
104
|
path = session_path(env_name)
|
|
105
|
+
data = asdict(session)
|
|
106
|
+
if _keyring_set(env_name, session.refresh_token):
|
|
107
|
+
del data["refresh_token"]
|
|
108
|
+
else:
|
|
109
|
+
logger.warning("No usable OS keyring; storing the refresh token in %s (mode 0600)", path)
|
|
62
110
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
111
|
fd, tmp_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
|
64
112
|
try:
|
|
65
113
|
os.chmod(tmp_name, SESSION_FILE_MODE)
|
|
66
114
|
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
67
|
-
json.dump(
|
|
115
|
+
json.dump(data, f, indent=2)
|
|
68
116
|
os.replace(tmp_name, path)
|
|
69
117
|
except Exception:
|
|
70
118
|
Path(tmp_name).unlink(missing_ok=True)
|
|
@@ -76,7 +124,10 @@ def write_session(env_name: str, session: Session) -> Path:
|
|
|
76
124
|
|
|
77
125
|
|
|
78
126
|
def read_session(env_name: str) -> Session | None:
|
|
79
|
-
"""Load a cached login session. Returns None if missing or unreadable.
|
|
127
|
+
"""Load a cached login session. Returns None if missing or unreadable.
|
|
128
|
+
|
|
129
|
+
A refresh token in the file (keyring fallback) wins over the keyring.
|
|
130
|
+
"""
|
|
80
131
|
try:
|
|
81
132
|
with open(session_path(env_name), encoding="utf-8") as f:
|
|
82
133
|
data = json.load(f)
|
|
@@ -86,6 +137,11 @@ def read_session(env_name: str) -> Session | None:
|
|
|
86
137
|
return None
|
|
87
138
|
field_names = {f.name for f in fields(Session)}
|
|
88
139
|
kwargs = {k: v for k, v in data.items() if k in field_names}
|
|
140
|
+
if not kwargs.get("refresh_token"):
|
|
141
|
+
token = _keyring_get(env_name)
|
|
142
|
+
if token is None:
|
|
143
|
+
return None
|
|
144
|
+
kwargs["refresh_token"] = token
|
|
89
145
|
try:
|
|
90
146
|
return Session(**kwargs)
|
|
91
147
|
except TypeError:
|
|
@@ -95,3 +151,4 @@ def read_session(env_name: str) -> Session | None:
|
|
|
95
151
|
def clear_session(env_name: str) -> None:
|
|
96
152
|
"""Remove the cached session for an environment, if any."""
|
|
97
153
|
session_path(env_name).unlink(missing_ok=True)
|
|
154
|
+
_keyring_delete(env_name)
|
|
@@ -21,6 +21,9 @@ app = typer.Typer(
|
|
|
21
21
|
data_app = typer.Typer(help="Data operations")
|
|
22
22
|
app.add_typer(data_app, name="data")
|
|
23
23
|
|
|
24
|
+
auth_app = typer.Typer(help="Sign in to the data platform")
|
|
25
|
+
app.add_typer(auth_app, name="auth")
|
|
26
|
+
|
|
24
27
|
console = Console()
|
|
25
28
|
|
|
26
|
-
__all__ = ["app", "data_app", "console"]
|
|
29
|
+
__all__ = ["app", "auth_app", "data_app", "console"]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"""``sunstone
|
|
1
|
+
"""``sunstone auth login`` / ``logout`` / ``whoami`` — human sign-in sessions.
|
|
2
2
|
|
|
3
3
|
``login`` runs the PKCE browser flow (:mod:`sunstone_data.auth.pkce`) and
|
|
4
|
-
caches only the refresh token, per environment,
|
|
5
|
-
minted on demand and never
|
|
6
|
-
not live token claims.
|
|
4
|
+
caches only the refresh token, per environment, in the OS keyring (see
|
|
5
|
+
:mod:`sunstone_data.auth.store`). Access tokens are minted on demand and never
|
|
6
|
+
stored, so ``whoami`` reports what was cached, not live token claims.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import typer
|
|
12
12
|
|
|
13
|
-
from sunstone_data.cli._app import
|
|
13
|
+
from sunstone_data.cli._app import auth_app, console
|
|
14
14
|
|
|
15
15
|
_ENV_OPTION_HELP = "Environment to act on (defaults to the active sunstone environment)."
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ def _resolve_env(env: str | None) -> str:
|
|
|
21
21
|
return env or current_environment_name()
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
@
|
|
24
|
+
@auth_app.command("login")
|
|
25
25
|
def login_cmd(
|
|
26
26
|
env: str | None = typer.Option(None, "--env", help=_ENV_OPTION_HELP),
|
|
27
27
|
) -> None:
|
|
@@ -78,7 +78,7 @@ def login_cmd(
|
|
|
78
78
|
console.print(f"[green]Logged in to '{env_name}'.[/green]")
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
@
|
|
81
|
+
@auth_app.command("logout")
|
|
82
82
|
def logout_cmd(
|
|
83
83
|
env: str | None = typer.Option(None, "--env", help=_ENV_OPTION_HELP),
|
|
84
84
|
) -> None:
|
|
@@ -94,7 +94,7 @@ def logout_cmd(
|
|
|
94
94
|
console.print(f"Not logged in to '{env_name}'; nothing to do.")
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
@
|
|
97
|
+
@auth_app.command("whoami")
|
|
98
98
|
def whoami_cmd(
|
|
99
99
|
env: str | None = typer.Option(None, "--env", help=_ENV_OPTION_HELP),
|
|
100
100
|
) -> None:
|
|
@@ -109,7 +109,7 @@ def whoami_cmd(
|
|
|
109
109
|
env_name = _resolve_env(env)
|
|
110
110
|
session = store.read_session(env_name)
|
|
111
111
|
if session is None:
|
|
112
|
-
console.print(f"Not logged in to '{env_name}'. Run `sunstone
|
|
112
|
+
console.print(f"Not logged in to '{env_name}'. Run `sunstone auth login`.")
|
|
113
113
|
return
|
|
114
114
|
|
|
115
115
|
age_hours = max(0.0, time.time() - session.cached_at) / 3600
|
|
@@ -14,16 +14,16 @@ Layout:
|
|
|
14
14
|
``@data_app.command(...)`` and lazy-imports its heavy dependencies
|
|
15
15
|
inside the function body.
|
|
16
16
|
|
|
17
|
-
This module re-exports ``app`` (the entry point) and ``data_app``
|
|
18
|
-
:mod:`sunstone_data.plugin`).
|
|
17
|
+
This module re-exports ``app`` (the entry point) and the ``data_app`` and
|
|
18
|
+
``auth_app`` groups (used by :mod:`sunstone_data.plugin`).
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
from sunstone_data.cli import commands # noqa: F401 (import side-effect: registers commands)
|
|
24
|
-
from sunstone_data.cli._app import app, console, data_app
|
|
24
|
+
from sunstone_data.cli._app import app, auth_app, console, data_app
|
|
25
25
|
|
|
26
|
-
__all__ = ["app", "console", "data_app"]
|
|
26
|
+
__all__ = ["app", "auth_app", "console", "data_app"]
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
if __name__ == "__main__":
|
|
@@ -109,8 +109,13 @@ def _try_resolve_environment() -> Any | None:
|
|
|
109
109
|
"""Resolve the active environment via sunstone-py.
|
|
110
110
|
|
|
111
111
|
Returns an ``Environment`` (sunstone-py 1.12+) if available, else None.
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
Missing sunstone-py and unexpected errors are logged at debug level and
|
|
113
|
+
return None so the caller falls back to ``.env`` files.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ValueError: The active environment is misconfigured (undefined name,
|
|
117
|
+
invalid section). Falling back to defaults would silently target
|
|
118
|
+
the wrong service.
|
|
114
119
|
"""
|
|
115
120
|
try:
|
|
116
121
|
from sunstone.env import resolve_environment
|
|
@@ -119,6 +124,8 @@ def _try_resolve_environment() -> Any | None:
|
|
|
119
124
|
except ImportError:
|
|
120
125
|
logger.debug("sunstone-py not installed, skipping environment resolution")
|
|
121
126
|
return None
|
|
127
|
+
except ValueError:
|
|
128
|
+
raise
|
|
122
129
|
except Exception:
|
|
123
130
|
logger.debug("Failed to resolve environment", exc_info=True)
|
|
124
131
|
return None
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/env_section.py
RENAMED
|
@@ -47,6 +47,13 @@ class DataPlatformSection:
|
|
|
47
47
|
:mod:`sunstone_data.discovery`.
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
|
+
skolem_base_iri: str | None = None
|
|
51
|
+
"""Base for skolem IRIs (``{base_iri}id/`` in deployed environments).
|
|
52
|
+
|
|
53
|
+
Accepted so fleet-distributed configs that set it still load. The client
|
|
54
|
+
does not use it.
|
|
55
|
+
"""
|
|
56
|
+
|
|
50
57
|
graph_url: str | None = None
|
|
51
58
|
"""Deprecated alias for ``base_iri``. Kept so existing config files still load."""
|
|
52
59
|
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/client.py
RENAMED
|
@@ -31,7 +31,7 @@ N_TRIPLES = "application/n-triples"
|
|
|
31
31
|
class AuthError(RuntimeError):
|
|
32
32
|
"""Not authenticated: no token, or the token was rejected twice."""
|
|
33
33
|
|
|
34
|
-
def __init__(self, message: str = "Not authenticated; run `sunstone
|
|
34
|
+
def __init__(self, message: str = "Not authenticated; run `sunstone auth login`"):
|
|
35
35
|
super().__init__(message)
|
|
36
36
|
|
|
37
37
|
|
|
@@ -314,7 +314,7 @@ class GraphClient:
|
|
|
314
314
|
status = resp.status_code
|
|
315
315
|
if status == 401:
|
|
316
316
|
if parse_challenge(resp.headers.get("WWW-Authenticate")) is not None:
|
|
317
|
-
return StepUpRequired(f"{method} {path} needs a fresh passkey sign-in; run `sunstone
|
|
317
|
+
return StepUpRequired(f"{method} {path} needs a fresh passkey sign-in; run `sunstone auth login`")
|
|
318
318
|
return AuthError()
|
|
319
319
|
if status == 403:
|
|
320
320
|
return PermissionError(_required_role(method, path), self._client_id)
|
|
@@ -10,24 +10,24 @@ from sunstone_data.env_section import DataPlatformSection, DataPlatformSectionPr
|
|
|
10
10
|
class SunstoneDataPlugin:
|
|
11
11
|
"""Data platform plugin for sunstone-py.
|
|
12
12
|
|
|
13
|
-
Implements CLIProvider (mounts `data` subcommand
|
|
13
|
+
Implements CLIProvider (mounts the `data` and `auth` subcommand groups), URLHandler
|
|
14
14
|
(resolves sunstone: URLs to parquet data), and EnvSectionProvider
|
|
15
15
|
(owns the ``data-platform`` subtable in env config files).
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
def cli_groups(self):
|
|
19
|
-
"""Return the data CLI
|
|
19
|
+
"""Return the data and auth CLI groups for mounting on the sunstone app.
|
|
20
20
|
|
|
21
21
|
Also attaches ``init`` and ``refresh`` to sunstone-py's ``env`` group.
|
|
22
22
|
sunstone-py calls this only after ``sunstone.cli.env_app`` exists, so
|
|
23
23
|
importing ``sunstone.cli`` here cannot re-enter plugin discovery.
|
|
24
24
|
"""
|
|
25
|
-
from sunstone_data.cli.main import data_app
|
|
25
|
+
from sunstone_data.cli.main import auth_app, data_app
|
|
26
26
|
from sunstone_data.env_commands import _register
|
|
27
27
|
|
|
28
28
|
_register()
|
|
29
29
|
|
|
30
|
-
return [("data", data_app)]
|
|
30
|
+
return [("data", data_app), ("auth", auth_app)]
|
|
31
31
|
|
|
32
32
|
def can_handle(self, url: str) -> bool:
|
|
33
33
|
"""Return True for sunstone: URLs.
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/auth/client_creds.py
RENAMED
|
File without changes
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/__init__.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/_errors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/ls.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/prov.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/refs.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/cli/commands/show.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/discovery.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/env_commands.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/__init__.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/dataset.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/graph/terms.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/identifiers.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/__init__.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/fetcher.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/owner.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/parser.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/pipeline.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/import_/storage.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/__init__.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/builder.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/metadata/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/__init__.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/checksum.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/encoding.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_data/utils/naming.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/definitions.py
RENAMED
|
File without changes
|
{sunstone_data_platform-1.2.0 → sunstone_data_platform-1.3.0}/src/sunstone_pipeline/io_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|