juniper-data-client 0.4.2__tar.gz → 0.5.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.
- juniper_data_client-0.5.0/PKG-INFO +119 -0
- juniper_data_client-0.5.0/README.md +74 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/__init__.py +7 -2
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/client.py +98 -6
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/constants.py +194 -6
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/contract.py +30 -20
- juniper_data_client-0.5.0/juniper_data_client/exceptions.py +101 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/testing/__init__.py +1 -1
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/testing/fake_client.py +37 -18
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/testing/generators.py +28 -17
- juniper_data_client-0.5.0/juniper_data_client.egg-info/PKG-INFO +119 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client.egg-info/SOURCES.txt +7 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/pyproject.toml +2 -2
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_client.py +375 -0
- juniper_data_client-0.5.0/tests/test_client_coverage_gaps.py +218 -0
- juniper_data_client-0.5.0/tests/test_constants_all_drift.py +59 -0
- juniper_data_client-0.5.0/tests/test_contract.py +279 -0
- juniper_data_client-0.5.0/tests/test_create_dataset_signature.py +55 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_fake_client.py +41 -36
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_fake_client_batch.py +3 -2
- juniper_data_client-0.5.0/tests/test_file_header_versions.py +57 -0
- juniper_data_client-0.5.0/tests/test_generator_parity.py +285 -0
- juniper_data_client-0.5.0/tests/test_memory_budget_check.py +462 -0
- juniper_data_client-0.5.0/tests/test_npz_val_partition.py +208 -0
- juniper_data_client-0.5.0/tests/test_retry_policy.py +110 -0
- juniper_data_client-0.5.0/tests/test_version_lockstep.py +108 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_versioning.py +1 -1
- juniper_data_client-0.4.2/PKG-INFO +0 -194
- juniper_data_client-0.4.2/README.md +0 -149
- juniper_data_client-0.4.2/juniper_data_client/exceptions.py +0 -37
- juniper_data_client-0.4.2/juniper_data_client.egg-info/PKG-INFO +0 -194
- juniper_data_client-0.4.2/tests/test_contract.py +0 -116
- juniper_data_client-0.4.2/tests/test_generator_parity.py +0 -135
- juniper_data_client-0.4.2/tests/test_retry_policy.py +0 -54
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/LICENSE +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client/py.typed +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client.egg-info/dependency_links.txt +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client.egg-info/requires.txt +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/juniper_data_client.egg-info/top_level.txt +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/setup.cfg +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_create_dataset_tags_ttl.py +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_malformed_json_response.py +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_npzfile_resource_lifecycle.py +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_on_request_hook.py +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_performance.py +0 -0
- {juniper_data_client-0.4.2 → juniper_data_client-0.5.0}/tests/test_x_request_id_propagation.py +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: juniper-data-client
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: HTTP client for the JuniperData dataset generation service
|
|
5
|
+
Author: Paul Calnon
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pcalnon/juniper-data-client
|
|
8
|
+
Project-URL: Repository, https://github.com/pcalnon/juniper-data-client
|
|
9
|
+
Project-URL: Documentation, https://github.com/pcalnon/juniper-data-client#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/pcalnon/juniper-data-client/issues
|
|
11
|
+
Keywords: juniper,juniper-ml,juniper-ai,artificial-intelligence,dataset,machine-learning,api-client
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.24.0
|
|
27
|
+
Requires-Dist: requests>=2.28.0
|
|
28
|
+
Requires-Dist: urllib3>=2.0.0
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-timeout>=2.2.0; extra == "test"
|
|
33
|
+
Requires-Dist: responses>=0.23.0; extra == "test"
|
|
34
|
+
Requires-Dist: juniper-observability>=0.1.1; extra == "test"
|
|
35
|
+
Provides-Extra: observability
|
|
36
|
+
Requires-Dist: juniper-observability>=0.1.1; extra == "observability"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: juniper-data-client[test]; extra == "dev"
|
|
39
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
41
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: flake8>=7.0.0; extra == "dev"
|
|
43
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# juniper-data-client
|
|
47
|
+
|
|
48
|
+
[](https://pypi.org/project/juniper-data-client/)
|
|
49
|
+
[](https://www.python.org/)
|
|
50
|
+
[](./LICENSE)
|
|
51
|
+
|
|
52
|
+
**Python HTTP client for the juniper-data dataset-generation service.**
|
|
53
|
+
|
|
54
|
+
`juniper-data-client` is a thin, synchronous HTTP client for fetching datasets from a `juniper-data`
|
|
55
|
+
instance. It exposes a single `JuniperDataClient` class whose methods correspond to the REST surface:
|
|
56
|
+
generator schemas, dataset creation, tag-based filtering, batch operations, the named-version
|
|
57
|
+
registry, and download of the resulting NPZ artifacts as ready-to-train `numpy` arrays. It is the
|
|
58
|
+
canonical dataset-fetch interface used by `juniper-cascor` (training) and `juniper-canopy`
|
|
59
|
+
(visualisation), and it reads the platform's shared `X_train` / `y_train` / `X_val` / `y_val` /
|
|
60
|
+
`X_test` / `y_test` NPZ schema (all `float32`). `val` is
|
|
61
|
+
presence-conditional: `validate_npz_contract` skips a split the artifact does not
|
|
62
|
+
carry. Fake producers emit it (#187); live artifacts may omit it.
|
|
63
|
+
|
|
64
|
+
> **Part of the Juniper platform.** juniper-data-client is the Python HTTP client for the juniper-data
|
|
65
|
+
> dataset service in [Juniper](https://github.com/pcalnon/juniper-ml) — a multi-package ML research
|
|
66
|
+
> platform built around constructive (Cascade-Correlation) and recurrent neural networks. Point it at
|
|
67
|
+
> any running juniper-data instance.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install juniper-data-client
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Optional extras: `[observability]` adds X-Request-ID propagation via `juniper-observability`; `[test]`
|
|
76
|
+
and `[dev]` install the test and lint/type-check toolchains.
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from juniper_data_client import JuniperDataClient
|
|
82
|
+
|
|
83
|
+
with JuniperDataClient("http://localhost:8100") as client:
|
|
84
|
+
print(client.health_check()["status"])
|
|
85
|
+
|
|
86
|
+
result = client.create_spiral_dataset(n_spirals=2, n_points_per_spiral=100, noise=0.1, seed=42)
|
|
87
|
+
arrays = client.download_artifact_npz(result["dataset_id"])
|
|
88
|
+
|
|
89
|
+
print("train:", len(arrays["X_train"]), "test:", len(arrays["X_test"]))
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The artifacts come back as a dict of `float32` `numpy` arrays, ready for `torch.from_numpy(...)`.
|
|
93
|
+
|
|
94
|
+
## API
|
|
95
|
+
|
|
96
|
+
| Method | Purpose |
|
|
97
|
+
|--------|---------|
|
|
98
|
+
| `health_check()` / `is_ready()` / `wait_for_ready(...)` | Liveness / readiness probes |
|
|
99
|
+
| `list_generators()` / `get_generator_schema(name)` | Discover available generators + their schemas |
|
|
100
|
+
| `create_dataset(generator, params, ...)` | Create a dataset from any generator |
|
|
101
|
+
| `create_spiral_dataset(...)` | Convenience wrapper for the spiral generator |
|
|
102
|
+
| `download_artifact_npz(dataset_id)` / `download_artifact_bytes(dataset_id)` | Download as numpy arrays / raw bytes |
|
|
103
|
+
| `get_dataset_metadata(id)` / `get_preview(id, n)` / `delete_dataset(id)` | Inspect / preview / delete |
|
|
104
|
+
| `list_datasets(limit, offset)` | Paginated dataset listing |
|
|
105
|
+
| `list_versions(name)` / `get_latest(name)` | Named-version registry |
|
|
106
|
+
| `batch_create(...)` / `batch_delete(...)` / `batch_update_tags(...)` / `batch_export(...)` | Batch operations |
|
|
107
|
+
|
|
108
|
+
## Status
|
|
109
|
+
|
|
110
|
+
**Beta** on PyPI. The current version is shown by the badge above; see [`CHANGELOG.md`](./CHANGELOG.md).
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
- [`docs/QUICK_START.md`](./docs/QUICK_START.md) — installation and verification guide
|
|
115
|
+
- [`docs/REFERENCE.md`](./docs/REFERENCE.md) — full API reference, configuration, and error model
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# juniper-data-client
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/juniper-data-client/)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
**Python HTTP client for the juniper-data dataset-generation service.**
|
|
8
|
+
|
|
9
|
+
`juniper-data-client` is a thin, synchronous HTTP client for fetching datasets from a `juniper-data`
|
|
10
|
+
instance. It exposes a single `JuniperDataClient` class whose methods correspond to the REST surface:
|
|
11
|
+
generator schemas, dataset creation, tag-based filtering, batch operations, the named-version
|
|
12
|
+
registry, and download of the resulting NPZ artifacts as ready-to-train `numpy` arrays. It is the
|
|
13
|
+
canonical dataset-fetch interface used by `juniper-cascor` (training) and `juniper-canopy`
|
|
14
|
+
(visualisation), and it reads the platform's shared `X_train` / `y_train` / `X_val` / `y_val` /
|
|
15
|
+
`X_test` / `y_test` NPZ schema (all `float32`). `val` is
|
|
16
|
+
presence-conditional: `validate_npz_contract` skips a split the artifact does not
|
|
17
|
+
carry. Fake producers emit it (#187); live artifacts may omit it.
|
|
18
|
+
|
|
19
|
+
> **Part of the Juniper platform.** juniper-data-client is the Python HTTP client for the juniper-data
|
|
20
|
+
> dataset service in [Juniper](https://github.com/pcalnon/juniper-ml) — a multi-package ML research
|
|
21
|
+
> platform built around constructive (Cascade-Correlation) and recurrent neural networks. Point it at
|
|
22
|
+
> any running juniper-data instance.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install juniper-data-client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Optional extras: `[observability]` adds X-Request-ID propagation via `juniper-observability`; `[test]`
|
|
31
|
+
and `[dev]` install the test and lint/type-check toolchains.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from juniper_data_client import JuniperDataClient
|
|
37
|
+
|
|
38
|
+
with JuniperDataClient("http://localhost:8100") as client:
|
|
39
|
+
print(client.health_check()["status"])
|
|
40
|
+
|
|
41
|
+
result = client.create_spiral_dataset(n_spirals=2, n_points_per_spiral=100, noise=0.1, seed=42)
|
|
42
|
+
arrays = client.download_artifact_npz(result["dataset_id"])
|
|
43
|
+
|
|
44
|
+
print("train:", len(arrays["X_train"]), "test:", len(arrays["X_test"]))
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The artifacts come back as a dict of `float32` `numpy` arrays, ready for `torch.from_numpy(...)`.
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
| Method | Purpose |
|
|
52
|
+
|--------|---------|
|
|
53
|
+
| `health_check()` / `is_ready()` / `wait_for_ready(...)` | Liveness / readiness probes |
|
|
54
|
+
| `list_generators()` / `get_generator_schema(name)` | Discover available generators + their schemas |
|
|
55
|
+
| `create_dataset(generator, params, ...)` | Create a dataset from any generator |
|
|
56
|
+
| `create_spiral_dataset(...)` | Convenience wrapper for the spiral generator |
|
|
57
|
+
| `download_artifact_npz(dataset_id)` / `download_artifact_bytes(dataset_id)` | Download as numpy arrays / raw bytes |
|
|
58
|
+
| `get_dataset_metadata(id)` / `get_preview(id, n)` / `delete_dataset(id)` | Inspect / preview / delete |
|
|
59
|
+
| `list_datasets(limit, offset)` | Paginated dataset listing |
|
|
60
|
+
| `list_versions(name)` / `get_latest(name)` | Named-version registry |
|
|
61
|
+
| `batch_create(...)` / `batch_delete(...)` / `batch_update_tags(...)` / `batch_export(...)` | Batch operations |
|
|
62
|
+
|
|
63
|
+
## Status
|
|
64
|
+
|
|
65
|
+
**Beta** on PyPI. The current version is shown by the badge above; see [`CHANGELOG.md`](./CHANGELOG.md).
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
- [`docs/QUICK_START.md`](./docs/QUICK_START.md) — installation and verification guide
|
|
70
|
+
- [`docs/REFERENCE.md`](./docs/REFERENCE.md) — full API reference, configuration, and error model
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -5,10 +5,11 @@ dataset generation service, used by both JuniperCascor and juniper-canopy.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from juniper_data_client.client import JuniperDataClient, RequestHook
|
|
8
|
+
from juniper_data_client.constants import ContractKind
|
|
8
9
|
from juniper_data_client.contract import validate_npz_contract
|
|
9
|
-
from juniper_data_client.exceptions import JuniperDataClientError, JuniperDataConfigurationError, JuniperDataConnectionError, JuniperDataNotFoundError, JuniperDataTimeoutError, JuniperDataValidationError
|
|
10
|
+
from juniper_data_client.exceptions import JuniperDataClientError, JuniperDataConfigurationError, JuniperDataConnectionError, JuniperDataContractError, JuniperDataNotFoundError, JuniperDataTimeoutError, JuniperDataValidationError
|
|
10
11
|
|
|
11
|
-
__version__ = "0.
|
|
12
|
+
__version__ = "0.5.0"
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"JuniperDataClient",
|
|
@@ -16,11 +17,15 @@ __all__ = [
|
|
|
16
17
|
"JuniperDataClientError",
|
|
17
18
|
"JuniperDataConfigurationError",
|
|
18
19
|
"JuniperDataConnectionError",
|
|
20
|
+
"JuniperDataContractError",
|
|
19
21
|
"JuniperDataNotFoundError",
|
|
20
22
|
"JuniperDataTimeoutError",
|
|
21
23
|
"JuniperDataValidationError",
|
|
22
24
|
# METRICS-MON R4.3 / seed-13: instrumentation hook type alias
|
|
23
25
|
# exported so consumers can type their hook closures.
|
|
24
26
|
"RequestHook",
|
|
27
|
+
# APD-DCLIENT-006: the validate_npz_contract return Literal, exported so
|
|
28
|
+
# consumers can annotate and exhaustively match on the result.
|
|
29
|
+
"ContractKind",
|
|
25
30
|
"__version__",
|
|
26
31
|
]
|
|
@@ -23,6 +23,7 @@ from juniper_data_client.constants import (
|
|
|
23
23
|
API_KEY_HEADER_NAME,
|
|
24
24
|
API_VERSION_PATH_SUFFIX,
|
|
25
25
|
DEFAULT_BACKOFF_FACTOR,
|
|
26
|
+
DEFAULT_BACKOFF_JITTER,
|
|
26
27
|
DEFAULT_BASE_URL,
|
|
27
28
|
DEFAULT_LIST_LIMIT,
|
|
28
29
|
DEFAULT_LIST_OFFSET,
|
|
@@ -62,7 +63,7 @@ from juniper_data_client.constants import (
|
|
|
62
63
|
SPIRAL_TRAIN_RATIO_DEFAULT,
|
|
63
64
|
URL_SCHEME_PREFIXES,
|
|
64
65
|
)
|
|
65
|
-
from juniper_data_client.exceptions import JuniperDataClientError, JuniperDataConnectionError, JuniperDataNotFoundError, JuniperDataTimeoutError, JuniperDataValidationError
|
|
66
|
+
from juniper_data_client.exceptions import JuniperDataClientError, JuniperDataConfigurationError, JuniperDataConnectionError, JuniperDataNotFoundError, JuniperDataTimeoutError, JuniperDataValidationError
|
|
66
67
|
|
|
67
68
|
logger = logging.getLogger("juniper_data_client.client")
|
|
68
69
|
|
|
@@ -122,6 +123,42 @@ def _noop_request_hook(
|
|
|
122
123
|
"""
|
|
123
124
|
|
|
124
125
|
|
|
126
|
+
def _render_error_detail(detail: Any) -> str:
|
|
127
|
+
"""Render a server ``detail`` payload as readable text for an error message.
|
|
128
|
+
|
|
129
|
+
FastAPI answers a validation failure with a *list* of error objects, e.g.::
|
|
130
|
+
|
|
131
|
+
[{"type": "missing", "loc": ["body", "seed"], "msg": "Field required"}]
|
|
132
|
+
|
|
133
|
+
Interpolating that list into an f-string produced a Python repr -- valid
|
|
134
|
+
text, but nothing a caller could parse and nothing a human could read at a
|
|
135
|
+
glance (defect-register ``APD-DCLIENT-003``). This renders the same content
|
|
136
|
+
as ``body.seed: Field required``, while the untouched structure stays
|
|
137
|
+
available on ``exc.detail``.
|
|
138
|
+
|
|
139
|
+
Anything that is not a list of mappings is returned via ``str`` unchanged,
|
|
140
|
+
so plain-string details and unexpected shapes both survive intact.
|
|
141
|
+
"""
|
|
142
|
+
if not isinstance(detail, list):
|
|
143
|
+
return str(detail)
|
|
144
|
+
|
|
145
|
+
rendered: list[str] = []
|
|
146
|
+
for item in detail:
|
|
147
|
+
if not isinstance(item, dict):
|
|
148
|
+
rendered.append(str(item))
|
|
149
|
+
continue
|
|
150
|
+
msg = item.get("msg", "")
|
|
151
|
+
loc = item.get("loc")
|
|
152
|
+
if isinstance(loc, (list, tuple)) and loc:
|
|
153
|
+
location = ".".join(str(part) for part in loc)
|
|
154
|
+
rendered.append(f"{location}: {msg}" if msg else location)
|
|
155
|
+
else:
|
|
156
|
+
rendered.append(str(msg) if msg else str(item))
|
|
157
|
+
# An empty list is a degenerate but legal payload; ``str`` keeps it visible
|
|
158
|
+
# rather than collapsing it to an empty message.
|
|
159
|
+
return "; ".join(rendered) if rendered else str(detail)
|
|
160
|
+
|
|
161
|
+
|
|
125
162
|
class JuniperDataClient:
|
|
126
163
|
"""Client for interacting with the JuniperData REST API.
|
|
127
164
|
|
|
@@ -185,13 +222,28 @@ class JuniperDataClient:
|
|
|
185
222
|
|
|
186
223
|
Returns:
|
|
187
224
|
Normalized URL with scheme, no trailing slash, no /v1 suffix
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
JuniperDataConfigurationError: when the URL carries no host (an
|
|
228
|
+
empty string, a bare scheme, a path-only value, or a
|
|
229
|
+
userinfo-only authority like ``http://user:secret@``) — a
|
|
230
|
+
misconfiguration that would otherwise normalize to a broken,
|
|
231
|
+
hostless URL and fail opaquely on the first request
|
|
232
|
+
(``APD-DCLIENT-004``; mirrors juniper-recurrence-client).
|
|
188
233
|
"""
|
|
189
234
|
url = url.strip()
|
|
190
235
|
|
|
191
|
-
|
|
236
|
+
# Scheme matching is case-insensitive (RFC 3986 §3.1): a case-sensitive
|
|
237
|
+
# startswith would re-prefix "HTTPS://host" into "http://HTTPS://host",
|
|
238
|
+
# silently downgrading TLS and sending the API key to hostname "https".
|
|
239
|
+
if not url.lower().startswith(URL_SCHEME_PREFIXES):
|
|
192
240
|
url = f"{DEFAULT_URL_SCHEME_PREFIX}{url}"
|
|
193
241
|
|
|
194
242
|
parsed = urlparse(url)
|
|
243
|
+
# hostname, not netloc: netloc accepts a userinfo-only authority
|
|
244
|
+
# ("user:secret@") as truthy while hostname is None for it.
|
|
245
|
+
if not parsed.hostname:
|
|
246
|
+
raise JuniperDataConfigurationError(f"base_url must include a host; got {url!r}")
|
|
195
247
|
normalized = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
|
|
196
248
|
normalized = normalized.rstrip("/")
|
|
197
249
|
|
|
@@ -211,6 +263,8 @@ class JuniperDataClient:
|
|
|
211
263
|
retry_strategy = Retry(
|
|
212
264
|
total=self.retries,
|
|
213
265
|
backoff_factor=self.backoff_factor,
|
|
266
|
+
# APD-ECO-002: decorrelate retry schedules across client instances.
|
|
267
|
+
backoff_jitter=DEFAULT_BACKOFF_JITTER,
|
|
214
268
|
status_forcelist=RETRYABLE_STATUS_CODES,
|
|
215
269
|
allowed_methods=RETRY_ALLOWED_METHODS,
|
|
216
270
|
)
|
|
@@ -309,14 +363,46 @@ class JuniperDataClient:
|
|
|
309
363
|
# fall back to using the raw response text as the error detail.
|
|
310
364
|
error_detail = response.text
|
|
311
365
|
|
|
366
|
+
# APD-DCLIENT-003: ``error_detail`` is whatever the server sent --
|
|
367
|
+
# a str for most handlers, but a list[dict] for FastAPI's 422. It is
|
|
368
|
+
# attached to the exception UNMODIFIED, and rendered separately for
|
|
369
|
+
# the human-readable message. Interpolating the list directly
|
|
370
|
+
# produced a Python repr no caller could parse, and which lost
|
|
371
|
+
# nothing only because there was nowhere else for the structure to
|
|
372
|
+
# go. Now there is: ``exc.detail``.
|
|
373
|
+
rendered_detail = _render_error_detail(error_detail)
|
|
374
|
+
|
|
375
|
+
# The context is passed explicitly at each site rather than unpacked
|
|
376
|
+
# from a shared ``**kwargs`` dict: mypy --strict infers such a dict
|
|
377
|
+
# as ``dict[str, object]`` and cannot match it against the typed
|
|
378
|
+
# keyword-only parameters, so the dict form silently gives up the
|
|
379
|
+
# type checking on exactly the arguments being added here.
|
|
312
380
|
if response.status_code == HTTP_404_NOT_FOUND:
|
|
313
|
-
outgoing_error = JuniperDataNotFoundError(
|
|
381
|
+
outgoing_error = JuniperDataNotFoundError(
|
|
382
|
+
f"Resource not found: {rendered_detail}",
|
|
383
|
+
status_code=response.status_code,
|
|
384
|
+
detail=error_detail,
|
|
385
|
+
response=response,
|
|
386
|
+
)
|
|
314
387
|
raise outgoing_error
|
|
315
388
|
elif response.status_code in (HTTP_400_BAD_REQUEST, HTTP_422_UNPROCESSABLE_ENTITY):
|
|
316
|
-
|
|
389
|
+
# APD-DCLIENT-001: the status code is what separates these two.
|
|
390
|
+
# It rides on the exception now, so a caller no longer has to
|
|
391
|
+
# substring-match the message to tell them apart.
|
|
392
|
+
outgoing_error = JuniperDataValidationError(
|
|
393
|
+
f"Validation error ({response.status_code}): {rendered_detail}",
|
|
394
|
+
status_code=response.status_code,
|
|
395
|
+
detail=error_detail,
|
|
396
|
+
response=response,
|
|
397
|
+
)
|
|
317
398
|
raise outgoing_error
|
|
318
399
|
else:
|
|
319
|
-
outgoing_error = JuniperDataClientError(
|
|
400
|
+
outgoing_error = JuniperDataClientError(
|
|
401
|
+
f"Request failed ({response.status_code}): {rendered_detail}",
|
|
402
|
+
status_code=response.status_code,
|
|
403
|
+
detail=error_detail,
|
|
404
|
+
response=response,
|
|
405
|
+
)
|
|
320
406
|
raise outgoing_error
|
|
321
407
|
finally:
|
|
322
408
|
duration_ms = (time.monotonic() - start) * 1000.0
|
|
@@ -413,6 +499,7 @@ class JuniperDataClient:
|
|
|
413
499
|
self,
|
|
414
500
|
generator: str,
|
|
415
501
|
params: Dict[str, Any],
|
|
502
|
+
*,
|
|
416
503
|
persist: bool = True,
|
|
417
504
|
name: Optional[str] = None,
|
|
418
505
|
description: Optional[str] = None,
|
|
@@ -423,6 +510,12 @@ class JuniperDataClient:
|
|
|
423
510
|
) -> Dict[str, Any]:
|
|
424
511
|
"""Create a new dataset via the JuniperData API.
|
|
425
512
|
|
|
513
|
+
``persist`` and everything after it are keyword-only (defect-register
|
|
514
|
+
``APD-DCLIENT-008``): with nine positional-or-keyword parameters the
|
|
515
|
+
call ``create_dataset("spiral", p, False)`` was legal and unreadable,
|
|
516
|
+
and any signature reordering would silently rebind arguments. Only
|
|
517
|
+
``generator`` and ``params`` — the universal pair — stay positional.
|
|
518
|
+
|
|
426
519
|
If a dataset with the same parameters already exists, the existing
|
|
427
520
|
dataset is returned (caching behavior).
|
|
428
521
|
|
|
@@ -551,7 +644,6 @@ class JuniperDataClient:
|
|
|
551
644
|
artifacts carry the standard keys:
|
|
552
645
|
- X_train, y_train: Training features and one-hot labels
|
|
553
646
|
- X_test, y_test: Test features and one-hot labels
|
|
554
|
-
- X_full, y_full: Full dataset features and one-hot labels
|
|
555
647
|
|
|
556
648
|
Additional keys may be present depending on the generator (WS-1 /
|
|
557
649
|
juniper-data#168):
|
|
@@ -9,18 +9,26 @@ Project: Juniper
|
|
|
9
9
|
Sub-Project: juniper-data-client
|
|
10
10
|
Application: JuniperDataClient
|
|
11
11
|
Author: Paul Calnon
|
|
12
|
-
Version: 0.
|
|
12
|
+
Version: 0.5.0
|
|
13
13
|
License: MIT License
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from typing import List, Tuple
|
|
16
|
+
from typing import Final, List, Literal, Tuple
|
|
17
17
|
|
|
18
|
+
# Complete export surface: every public module-level constant, in file order,
|
|
19
|
+
# grouped by the section headers below. Completeness is enforced by
|
|
20
|
+
# tests/test_constants_all_drift.py — a new constant that is not added here
|
|
21
|
+
# fails that gate (and, on any module WITH __all__, CodeQL flags touched
|
|
22
|
+
# non-exported globals as py/unused-global-variable, blocking the merge).
|
|
18
23
|
__all__ = [
|
|
24
|
+
# Service Configuration
|
|
19
25
|
"DEFAULT_BASE_URL",
|
|
20
26
|
"FAKE_BASE_URL",
|
|
27
|
+
# HTTP Configuration
|
|
21
28
|
"DEFAULT_TIMEOUT",
|
|
22
29
|
"DEFAULT_RETRIES",
|
|
23
30
|
"DEFAULT_BACKOFF_FACTOR",
|
|
31
|
+
"DEFAULT_BACKOFF_JITTER",
|
|
24
32
|
"RETRYABLE_STATUS_CODES",
|
|
25
33
|
"RETRY_ALLOWED_METHODS",
|
|
26
34
|
"HTTP_POOL_CONNECTIONS",
|
|
@@ -28,12 +36,15 @@ __all__ = [
|
|
|
28
36
|
"URL_SCHEME_PREFIXES",
|
|
29
37
|
"DEFAULT_URL_SCHEME_PREFIX",
|
|
30
38
|
"API_VERSION_PATH_SUFFIX",
|
|
39
|
+
# Readiness Polling
|
|
31
40
|
"DEFAULT_READY_TIMEOUT",
|
|
32
41
|
"DEFAULT_READY_POLL_INTERVAL",
|
|
33
42
|
"HEALTH_READY_STATUS",
|
|
43
|
+
# Authentication
|
|
34
44
|
"API_KEY_HEADER_NAME",
|
|
35
45
|
"API_KEY_ENV_VAR",
|
|
36
46
|
"API_KEY_FILE_ENV_VAR",
|
|
47
|
+
# REST Endpoints
|
|
37
48
|
"ENDPOINT_HEALTH",
|
|
38
49
|
"ENDPOINT_HEALTH_READY",
|
|
39
50
|
"ENDPOINT_GENERATORS",
|
|
@@ -43,6 +54,133 @@ __all__ = [
|
|
|
43
54
|
"ENDPOINT_DATASETS_LATEST",
|
|
44
55
|
"ENDPOINT_DATASET_BY_ID_TEMPLATE",
|
|
45
56
|
"ENDPOINT_DATASET_ARTIFACT_TEMPLATE",
|
|
57
|
+
"ENDPOINT_DATASET_PREVIEW_TEMPLATE",
|
|
58
|
+
"ENDPOINT_BATCH_CREATE",
|
|
59
|
+
"ENDPOINT_BATCH_DELETE",
|
|
60
|
+
"ENDPOINT_BATCH_TAGS",
|
|
61
|
+
"ENDPOINT_BATCH_EXPORT",
|
|
62
|
+
# HTTP Status Codes
|
|
63
|
+
"HTTP_400_BAD_REQUEST",
|
|
64
|
+
"HTTP_404_NOT_FOUND",
|
|
65
|
+
"HTTP_422_UNPROCESSABLE_ENTITY",
|
|
66
|
+
# Listing Defaults
|
|
67
|
+
"DEFAULT_LIST_LIMIT",
|
|
68
|
+
"DEFAULT_LIST_OFFSET",
|
|
69
|
+
# Preview Defaults
|
|
70
|
+
"DEFAULT_PREVIEW_N",
|
|
71
|
+
"MAX_PREVIEW_N",
|
|
72
|
+
# Data Type Contract
|
|
73
|
+
"DEFAULT_ARRAY_DTYPE",
|
|
74
|
+
# Generator Names
|
|
75
|
+
"GENERATOR_SPIRAL",
|
|
76
|
+
"GENERATOR_XOR",
|
|
77
|
+
"GENERATOR_CIRCLE",
|
|
78
|
+
"GENERATOR_CIRCLE_LEGACY",
|
|
79
|
+
"GENERATOR_MOON",
|
|
80
|
+
"GENERATOR_GAUSSIAN",
|
|
81
|
+
"GENERATOR_CHECKERBOARD",
|
|
82
|
+
"GENERATOR_CSV_IMPORT",
|
|
83
|
+
"GENERATOR_MNIST",
|
|
84
|
+
"GENERATOR_ARC_AGI",
|
|
85
|
+
"GENERATOR_EQUITIES",
|
|
86
|
+
"GENERATOR_EQUITIES_SEQ",
|
|
87
|
+
"GENERATOR_MULTI_SINE",
|
|
88
|
+
"GENERATOR_MACKEY_GLASS",
|
|
89
|
+
"GENERATOR_AR_P",
|
|
90
|
+
"GENERATOR_IRREGULAR_SINE",
|
|
91
|
+
"GENERATOR_DELAY_PRODUCT",
|
|
92
|
+
# Generator Catalog Metadata
|
|
93
|
+
"GENERATOR_VERSION",
|
|
94
|
+
"GENERATOR_DESCRIPTION_SPIRAL",
|
|
95
|
+
"GENERATOR_DESCRIPTION_XOR",
|
|
96
|
+
"GENERATOR_DESCRIPTION_CIRCLE",
|
|
97
|
+
"GENERATOR_DESCRIPTION_MOON",
|
|
98
|
+
"GENERATOR_DESCRIPTION_GAUSSIAN",
|
|
99
|
+
"GENERATOR_DESCRIPTION_CHECKERBOARD",
|
|
100
|
+
"GENERATOR_DESCRIPTION_CSV_IMPORT",
|
|
101
|
+
"GENERATOR_DESCRIPTION_MNIST",
|
|
102
|
+
"GENERATOR_DESCRIPTION_ARC_AGI",
|
|
103
|
+
"GENERATOR_DESCRIPTION_EQUITIES",
|
|
104
|
+
"GENERATOR_DESCRIPTION_EQUITIES_SEQ",
|
|
105
|
+
"GENERATOR_DESCRIPTION_MULTI_SINE",
|
|
106
|
+
"GENERATOR_DESCRIPTION_MACKEY_GLASS",
|
|
107
|
+
"GENERATOR_DESCRIPTION_AR_P",
|
|
108
|
+
"GENERATOR_DESCRIPTION_IRREGULAR_SINE",
|
|
109
|
+
"GENERATOR_DESCRIPTION_DELAY_PRODUCT",
|
|
110
|
+
# Generator Defaults — Spiral
|
|
111
|
+
"SPIRAL_N_SPIRALS_DEFAULT",
|
|
112
|
+
"SPIRAL_N_POINTS_PER_SPIRAL_DEFAULT",
|
|
113
|
+
"SPIRAL_NOISE_DEFAULT",
|
|
114
|
+
"SPIRAL_ALGORITHM_DEFAULT",
|
|
115
|
+
"SPIRAL_TRAIN_RATIO_DEFAULT",
|
|
116
|
+
"SPIRAL_N_SPIRALS_MIN",
|
|
117
|
+
"SPIRAL_N_POINTS_PER_SPIRAL_MIN",
|
|
118
|
+
"SPIRAL_NOISE_MIN",
|
|
119
|
+
"SPIRAL_TRAIN_RATIO_MIN",
|
|
120
|
+
"SPIRAL_TRAIN_RATIO_MAX",
|
|
121
|
+
"SPIRAL_ALGORITHM_MODERN",
|
|
122
|
+
"SPIRAL_ALGORITHM_LEGACY",
|
|
123
|
+
"SPIRAL_ALGORITHMS",
|
|
124
|
+
"SPIRAL_RADIUS_SCALE",
|
|
125
|
+
"SPIRAL_ANGLE_TURNS",
|
|
126
|
+
# Generator Defaults — XOR
|
|
127
|
+
"XOR_N_POINTS_DEFAULT",
|
|
128
|
+
"XOR_NOISE_DEFAULT",
|
|
129
|
+
"XOR_TRAIN_RATIO_DEFAULT",
|
|
130
|
+
"XOR_N_POINTS_MIN",
|
|
131
|
+
"XOR_NOISE_MIN",
|
|
132
|
+
"XOR_TRAIN_RATIO_MIN",
|
|
133
|
+
"XOR_TRAIN_RATIO_MAX",
|
|
134
|
+
"XOR_CORNERS",
|
|
135
|
+
"XOR_CORNER_LABELS",
|
|
136
|
+
"XOR_NUM_CORNERS",
|
|
137
|
+
"XOR_NUM_CLASSES",
|
|
138
|
+
# Generator Defaults — Circle
|
|
139
|
+
"CIRCLE_N_POINTS_DEFAULT",
|
|
140
|
+
"CIRCLE_NOISE_DEFAULT",
|
|
141
|
+
"CIRCLE_FACTOR_DEFAULT",
|
|
142
|
+
"CIRCLE_TRAIN_RATIO_DEFAULT",
|
|
143
|
+
"CIRCLE_N_POINTS_MIN",
|
|
144
|
+
"CIRCLE_NOISE_MIN",
|
|
145
|
+
"CIRCLE_FACTOR_MIN",
|
|
146
|
+
"CIRCLE_FACTOR_MAX",
|
|
147
|
+
"CIRCLE_TRAIN_RATIO_MIN",
|
|
148
|
+
"CIRCLE_TRAIN_RATIO_MAX",
|
|
149
|
+
"CIRCLE_NUM_CLASSES",
|
|
150
|
+
# Generator Defaults — Moon
|
|
151
|
+
"MOON_N_POINTS_DEFAULT",
|
|
152
|
+
"MOON_NOISE_DEFAULT",
|
|
153
|
+
"MOON_TRAIN_RATIO_DEFAULT",
|
|
154
|
+
"MOON_N_POINTS_MIN",
|
|
155
|
+
"MOON_NOISE_MIN",
|
|
156
|
+
"MOON_TRAIN_RATIO_MIN",
|
|
157
|
+
"MOON_TRAIN_RATIO_MAX",
|
|
158
|
+
"MOON_NUM_CLASSES",
|
|
159
|
+
"MOON_LOWER_X_OFFSET",
|
|
160
|
+
"MOON_LOWER_Y_OFFSET",
|
|
161
|
+
"MOON_LOWER_Y_SHIFT",
|
|
162
|
+
# Fake Service Identity
|
|
163
|
+
"FAKE_SERVICE_STATUS",
|
|
164
|
+
"FAKE_SERVICE_NAME",
|
|
165
|
+
"FAKE_SERVICE_VERSION",
|
|
166
|
+
"FAKE_SERVICE_UPTIME_SECONDS",
|
|
167
|
+
# NPZ Artifact Contract (WS-1 / juniper-data#168)
|
|
168
|
+
"NPZ_SPLITS",
|
|
169
|
+
"FAKE_VAL_RATIO_DEFAULT",
|
|
170
|
+
"NPZ_KEY_X",
|
|
171
|
+
"NPZ_KEY_Y",
|
|
172
|
+
"NPZ_KEY_Y_REG",
|
|
173
|
+
"NPZ_KEY_T",
|
|
174
|
+
"NPZ_KEY_DT",
|
|
175
|
+
"NPZ_KEY_TARGET_DT",
|
|
176
|
+
"NPZ_KEY_OBSERVED_MASK",
|
|
177
|
+
"NPZ_KEY_PADDING_MASK",
|
|
178
|
+
"NPZ_KEY_SEQ_LENGTHS",
|
|
179
|
+
"ContractKind",
|
|
180
|
+
"CONTRACT_KIND_TABULAR",
|
|
181
|
+
"CONTRACT_KIND_SEQUENCE",
|
|
182
|
+
"TASK_TYPE_CLASSIFICATION",
|
|
183
|
+
"TASK_TYPE_REGRESSION",
|
|
46
184
|
]
|
|
47
185
|
|
|
48
186
|
# ─── Service Configuration ───────────────────────────────────────────────────
|
|
@@ -55,6 +193,13 @@ FAKE_BASE_URL: str = "http://fake-data:8100"
|
|
|
55
193
|
DEFAULT_TIMEOUT: int = 30
|
|
56
194
|
DEFAULT_RETRIES: int = 3
|
|
57
195
|
DEFAULT_BACKOFF_FACTOR: float = 0.5
|
|
196
|
+
# APD-ECO-002: urllib3 applies this as an ABSOLUTE additive term --
|
|
197
|
+
# ``backoff_value += random.random() * backoff_jitter`` -- not a proportional
|
|
198
|
+
# one. Without it every client that trips the same transient outage retries on
|
|
199
|
+
# an identical schedule, so a service that is already failing is hit by a
|
|
200
|
+
# synchronised herd. Matched to DEFAULT_BACKOFF_FACTOR so the spread is a full
|
|
201
|
+
# window on the first retry, which is the step that carries the most callers.
|
|
202
|
+
DEFAULT_BACKOFF_JITTER: float = 0.5
|
|
58
203
|
RETRYABLE_STATUS_CODES: List[int] = [429, 500, 502, 503, 504]
|
|
59
204
|
# XREPO-11 (2026-04-24): auto-retry is now restricted to idempotent
|
|
60
205
|
# HTTP methods per RFC 9110 §9.2.2. POST, PATCH and DELETE were
|
|
@@ -148,6 +293,18 @@ GENERATOR_CHECKERBOARD: str = "checkerboard"
|
|
|
148
293
|
GENERATOR_CSV_IMPORT: str = "csv_import"
|
|
149
294
|
GENERATOR_MNIST: str = "mnist"
|
|
150
295
|
GENERATOR_ARC_AGI: str = "arc_agi"
|
|
296
|
+
# W-9 (CLI experimentation plan §11, 2026-08-08): added constants for the 7
|
|
297
|
+
# server generators the client still lacked — the equities pair plus the five
|
|
298
|
+
# sequence generators (juniper-data ≥ 0.8/0.9). The parity test now also
|
|
299
|
+
# cross-checks the pinned mirror against the LIVE server registry whenever
|
|
300
|
+
# juniper-data is importable, so this list can no longer drift silently.
|
|
301
|
+
GENERATOR_EQUITIES: str = "equities"
|
|
302
|
+
GENERATOR_EQUITIES_SEQ: str = "equities_seq"
|
|
303
|
+
GENERATOR_MULTI_SINE: str = "multi_sine"
|
|
304
|
+
GENERATOR_MACKEY_GLASS: str = "mackey_glass"
|
|
305
|
+
GENERATOR_AR_P: str = "ar_p"
|
|
306
|
+
GENERATOR_IRREGULAR_SINE: str = "irregular_sine"
|
|
307
|
+
GENERATOR_DELAY_PRODUCT: str = "delay_product"
|
|
151
308
|
|
|
152
309
|
# ─── Generator Catalog Metadata ──────────────────────────────────────────────
|
|
153
310
|
|
|
@@ -161,6 +318,13 @@ GENERATOR_DESCRIPTION_CHECKERBOARD: str = "Checkerboard pattern classification d
|
|
|
161
318
|
GENERATOR_DESCRIPTION_CSV_IMPORT: str = "CSV/JSON import for custom datasets"
|
|
162
319
|
GENERATOR_DESCRIPTION_MNIST: str = "MNIST and Fashion-MNIST digit classification dataset"
|
|
163
320
|
GENERATOR_DESCRIPTION_ARC_AGI: str = "ARC-AGI visual reasoning tasks dataset"
|
|
321
|
+
GENERATOR_DESCRIPTION_EQUITIES: str = "Real equities OHLCV tabular dataset (next-day direction)"
|
|
322
|
+
GENERATOR_DESCRIPTION_EQUITIES_SEQ: str = "Real equities irregular-Δt sequence dataset (calendar-gap Δt)"
|
|
323
|
+
GENERATOR_DESCRIPTION_MULTI_SINE: str = "Regular-Δt superimposed-sinusoid sequence dataset"
|
|
324
|
+
GENERATOR_DESCRIPTION_MACKEY_GLASS: str = "Regular-Δt Mackey-Glass chaotic sequence dataset"
|
|
325
|
+
GENERATOR_DESCRIPTION_AR_P: str = "Regular-Δt stable AR(p) linear-stochastic sequence dataset"
|
|
326
|
+
GENERATOR_DESCRIPTION_IRREGULAR_SINE: str = "Irregular-Δt superimposed-sinusoid sequence dataset"
|
|
327
|
+
GENERATOR_DESCRIPTION_DELAY_PRODUCT: str = "Irregular-Δt bilinear delay-product capacity sequence dataset"
|
|
164
328
|
|
|
165
329
|
# ─── Generator Defaults — Spiral ─────────────────────────────────────────────
|
|
166
330
|
|
|
@@ -255,7 +419,27 @@ FAKE_SERVICE_UPTIME_SECONDS: float = 0.0
|
|
|
255
419
|
|
|
256
420
|
# Split suffixes used for every per-split NPZ key (suffix a stem with
|
|
257
421
|
# f"{stem}_{split}", e.g. f"{NPZ_KEY_X}_train").
|
|
258
|
-
|
|
422
|
+
#
|
|
423
|
+
# "val" is the in-loop validation partition of the three-way train/val/test
|
|
424
|
+
# contract (design decision O-1). Membership here is presence-conditional --
|
|
425
|
+
# validate_npz_contract skips any split a given artifact does not carry -- so
|
|
426
|
+
# listing it does not require an artifact to provide it, and two-partition
|
|
427
|
+
# legacy artifacts keep validating unchanged.
|
|
428
|
+
#
|
|
429
|
+
# "full" is GONE (decision 11, 2026-09-05). The *_full family is no longer part of
|
|
430
|
+
# the contract: generators do not emit it and no consumer may require it.
|
|
431
|
+
#
|
|
432
|
+
# Removing it from this tuple does NOT stop a legacy artifact loading. Every stored
|
|
433
|
+
# artifact still carries X_full, and validate_npz_contract skips any split it does
|
|
434
|
+
# not list -- so an X_full that is present is simply not validated, which is what
|
|
435
|
+
# "tolerate, do not require" means in practice. Adding it back to get it validated
|
|
436
|
+
# would re-create the requirement this decision removed.
|
|
437
|
+
NPZ_SPLITS: Tuple[str, ...] = ("train", "val", "test")
|
|
438
|
+
|
|
439
|
+
# Validation share used by the synthetic generators in juniper_data_client.testing.
|
|
440
|
+
# Applied alongside each generator's train ratio, so the default 0.8 / 0.1 carve
|
|
441
|
+
# leaves the remainder to test.
|
|
442
|
+
FAKE_VAL_RATIO_DEFAULT: float = 0.1
|
|
259
443
|
|
|
260
444
|
# Canonical per-split key stems.
|
|
261
445
|
NPZ_KEY_X: str = "X" # features: (N, F) tabular or (W, L, F) sequence
|
|
@@ -268,9 +452,13 @@ NPZ_KEY_OBSERVED_MASK: str = "observed_mask" # 1=real, 0=imputed (sequence)
|
|
|
268
452
|
NPZ_KEY_PADDING_MASK: str = "padding_mask" # 1=valid, 0=structural padding (sequence)
|
|
269
453
|
NPZ_KEY_SEQ_LENGTHS: str = "seq_lengths" # valid step count per window (sequence)
|
|
270
454
|
|
|
271
|
-
# Contract discriminators returned by ``validate_npz_contract``.
|
|
272
|
-
|
|
273
|
-
|
|
455
|
+
# Contract discriminators returned by ``validate_npz_contract``. The Literal
|
|
456
|
+
# lets a caller exhaustively match on the result (APD-DCLIENT-006); annotating
|
|
457
|
+
# the constants with Final[ContractKind] keeps their values and the Literal's
|
|
458
|
+
# members from drifting apart -- a mismatch fails type checking.
|
|
459
|
+
ContractKind = Literal["tabular", "sequence"]
|
|
460
|
+
CONTRACT_KIND_TABULAR: Final[ContractKind] = "tabular"
|
|
461
|
+
CONTRACT_KIND_SEQUENCE: Final[ContractKind] = "sequence"
|
|
274
462
|
|
|
275
463
|
# ``task_type`` values carried in dataset metadata (meta.json, not the NPZ).
|
|
276
464
|
TASK_TYPE_CLASSIFICATION: str = "classification"
|