juniper-data 0.4.2__py3-none-any.whl
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/__init__.py +88 -0
- juniper_data/__main__.py +78 -0
- juniper_data/api/__init__.py +10 -0
- juniper_data/api/app.py +111 -0
- juniper_data/api/middleware.py +95 -0
- juniper_data/api/routes/__init__.py +9 -0
- juniper_data/api/routes/datasets.py +414 -0
- juniper_data/api/routes/generators.py +125 -0
- juniper_data/api/routes/health.py +49 -0
- juniper_data/api/security.py +238 -0
- juniper_data/api/settings.py +109 -0
- juniper_data/core/__init__.py +32 -0
- juniper_data/core/artifacts.py +63 -0
- juniper_data/core/dataset_id.py +38 -0
- juniper_data/core/models.py +135 -0
- juniper_data/core/split.py +120 -0
- juniper_data/generators/__init__.py +15 -0
- juniper_data/generators/arc_agi/__init__.py +11 -0
- juniper_data/generators/arc_agi/generator.py +229 -0
- juniper_data/generators/arc_agi/params.py +56 -0
- juniper_data/generators/checkerboard/__init__.py +15 -0
- juniper_data/generators/checkerboard/generator.py +114 -0
- juniper_data/generators/checkerboard/params.py +32 -0
- juniper_data/generators/circles/__init__.py +11 -0
- juniper_data/generators/circles/generator.py +112 -0
- juniper_data/generators/circles/params.py +31 -0
- juniper_data/generators/csv_import/__init__.py +15 -0
- juniper_data/generators/csv_import/generator.py +198 -0
- juniper_data/generators/csv_import/params.py +48 -0
- juniper_data/generators/gaussian/__init__.py +11 -0
- juniper_data/generators/gaussian/generator.py +149 -0
- juniper_data/generators/gaussian/params.py +53 -0
- juniper_data/generators/mnist/__init__.py +11 -0
- juniper_data/generators/mnist/generator.py +124 -0
- juniper_data/generators/mnist/params.py +39 -0
- juniper_data/generators/spiral/__init__.py +57 -0
- juniper_data/generators/spiral/defaults.py +39 -0
- juniper_data/generators/spiral/generator.py +206 -0
- juniper_data/generators/spiral/params.py +148 -0
- juniper_data/generators/xor/__init__.py +11 -0
- juniper_data/generators/xor/generator.py +162 -0
- juniper_data/generators/xor/params.py +30 -0
- juniper_data/storage/__init__.py +120 -0
- juniper_data/storage/base.py +279 -0
- juniper_data/storage/cached.py +211 -0
- juniper_data/storage/hf_store.py +257 -0
- juniper_data/storage/kaggle_store.py +333 -0
- juniper_data/storage/local_fs.py +232 -0
- juniper_data/storage/memory.py +136 -0
- juniper_data/storage/postgres_store.py +373 -0
- juniper_data/storage/redis_store.py +264 -0
- juniper_data/tests/__init__.py +1 -0
- juniper_data/tests/conftest.py +68 -0
- juniper_data/tests/fixtures/generate_golden_datasets.py +199 -0
- juniper_data/tests/integration/__init__.py +1 -0
- juniper_data/tests/integration/test_api.py +283 -0
- juniper_data/tests/integration/test_e2e_workflow.py +378 -0
- juniper_data/tests/integration/test_lifecycle_api.py +304 -0
- juniper_data/tests/integration/test_security_integration.py +189 -0
- juniper_data/tests/integration/test_storage_workflow.py +259 -0
- juniper_data/tests/performance/__init__.py +1 -0
- juniper_data/tests/performance/test_generator_benchmarks.py +178 -0
- juniper_data/tests/performance/test_storage_benchmarks.py +257 -0
- juniper_data/tests/unit/__init__.py +1 -0
- juniper_data/tests/unit/test_api_app.py +206 -0
- juniper_data/tests/unit/test_api_routes.py +407 -0
- juniper_data/tests/unit/test_api_settings.py +100 -0
- juniper_data/tests/unit/test_arc_agi_generator.py +525 -0
- juniper_data/tests/unit/test_artifacts.py +145 -0
- juniper_data/tests/unit/test_cached_store.py +423 -0
- juniper_data/tests/unit/test_checkerboard_generator.py +232 -0
- juniper_data/tests/unit/test_circles_generator.py +256 -0
- juniper_data/tests/unit/test_csv_import_generator.py +345 -0
- juniper_data/tests/unit/test_dataset_id.py +181 -0
- juniper_data/tests/unit/test_gaussian_generator.py +333 -0
- juniper_data/tests/unit/test_hf_store.py +416 -0
- juniper_data/tests/unit/test_init.py +93 -0
- juniper_data/tests/unit/test_kaggle_store.py +469 -0
- juniper_data/tests/unit/test_lifecycle.py +394 -0
- juniper_data/tests/unit/test_main.py +127 -0
- juniper_data/tests/unit/test_middleware.py +79 -0
- juniper_data/tests/unit/test_mnist_generator.py +370 -0
- juniper_data/tests/unit/test_postgres_store.py +490 -0
- juniper_data/tests/unit/test_redis_store.py +500 -0
- juniper_data/tests/unit/test_security.py +281 -0
- juniper_data/tests/unit/test_security_boundaries.py +517 -0
- juniper_data/tests/unit/test_spiral_generator.py +566 -0
- juniper_data/tests/unit/test_split.py +245 -0
- juniper_data/tests/unit/test_storage.py +767 -0
- juniper_data/tests/unit/test_xor_generator.py +223 -0
- juniper_data-0.4.2.dist-info/METADATA +216 -0
- juniper_data-0.4.2.dist-info/RECORD +95 -0
- juniper_data-0.4.2.dist-info/WHEEL +5 -0
- juniper_data-0.4.2.dist-info/licenses/LICENSE +9 -0
- juniper_data-0.4.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Unit tests for the XOR dataset generator."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from juniper_data.generators.xor import VERSION, XorGenerator, XorParams, get_schema
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.unit
|
|
10
|
+
@pytest.mark.generators
|
|
11
|
+
class TestXorParams:
|
|
12
|
+
"""Tests for XorParams validation."""
|
|
13
|
+
|
|
14
|
+
def test_default_params(self) -> None:
|
|
15
|
+
"""Default parameters are valid."""
|
|
16
|
+
params = XorParams()
|
|
17
|
+
assert params.n_points_per_quadrant == 50
|
|
18
|
+
assert params.x_range == 1.0
|
|
19
|
+
assert params.y_range == 1.0
|
|
20
|
+
assert params.margin == 0.1
|
|
21
|
+
assert params.noise == 0.0
|
|
22
|
+
assert params.train_ratio == 0.8
|
|
23
|
+
assert params.test_ratio == 0.2
|
|
24
|
+
|
|
25
|
+
def test_custom_params(self) -> None:
|
|
26
|
+
"""Custom parameters are accepted."""
|
|
27
|
+
params = XorParams(
|
|
28
|
+
n_points_per_quadrant=100,
|
|
29
|
+
x_range=2.0,
|
|
30
|
+
y_range=3.0,
|
|
31
|
+
margin=0.2,
|
|
32
|
+
noise=0.1,
|
|
33
|
+
seed=42,
|
|
34
|
+
train_ratio=0.7,
|
|
35
|
+
test_ratio=0.3,
|
|
36
|
+
)
|
|
37
|
+
assert params.n_points_per_quadrant == 100
|
|
38
|
+
assert params.x_range == 2.0
|
|
39
|
+
assert params.seed == 42
|
|
40
|
+
|
|
41
|
+
def test_invalid_n_points(self) -> None:
|
|
42
|
+
"""n_points_per_quadrant must be >= 1."""
|
|
43
|
+
with pytest.raises(ValueError):
|
|
44
|
+
XorParams(n_points_per_quadrant=0)
|
|
45
|
+
|
|
46
|
+
def test_invalid_x_range(self) -> None:
|
|
47
|
+
"""x_range must be > 0."""
|
|
48
|
+
with pytest.raises(ValueError):
|
|
49
|
+
XorParams(x_range=0)
|
|
50
|
+
|
|
51
|
+
def test_invalid_train_ratio(self) -> None:
|
|
52
|
+
"""train_ratio must be in (0, 1]."""
|
|
53
|
+
with pytest.raises(ValueError):
|
|
54
|
+
XorParams(train_ratio=0)
|
|
55
|
+
with pytest.raises(ValueError):
|
|
56
|
+
XorParams(train_ratio=1.5)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.unit
|
|
60
|
+
@pytest.mark.generators
|
|
61
|
+
class TestXorGenerator:
|
|
62
|
+
"""Tests for XorGenerator functionality."""
|
|
63
|
+
|
|
64
|
+
def test_generate_correct_shapes(self) -> None:
|
|
65
|
+
"""Generated arrays have correct shapes."""
|
|
66
|
+
params = XorParams(n_points_per_quadrant=25, seed=42)
|
|
67
|
+
result = XorGenerator.generate(params)
|
|
68
|
+
|
|
69
|
+
n_total = 4 * 25
|
|
70
|
+
n_train = int(n_total * 0.8)
|
|
71
|
+
n_test = n_total - n_train
|
|
72
|
+
|
|
73
|
+
assert result["X_train"].shape == (n_train, 2)
|
|
74
|
+
assert result["y_train"].shape == (n_train, 2)
|
|
75
|
+
assert result["X_test"].shape == (n_test, 2)
|
|
76
|
+
assert result["y_test"].shape == (n_test, 2)
|
|
77
|
+
assert result["X_full"].shape == (n_total, 2)
|
|
78
|
+
assert result["y_full"].shape == (n_total, 2)
|
|
79
|
+
|
|
80
|
+
def test_generate_correct_dtypes(self) -> None:
|
|
81
|
+
"""Generated arrays have float32 dtype."""
|
|
82
|
+
params = XorParams(n_points_per_quadrant=10, seed=42)
|
|
83
|
+
result = XorGenerator.generate(params)
|
|
84
|
+
|
|
85
|
+
for key in result:
|
|
86
|
+
assert result[key].dtype == np.float32
|
|
87
|
+
|
|
88
|
+
def test_generate_deterministic_with_seed(self) -> None:
|
|
89
|
+
"""Same seed produces identical data."""
|
|
90
|
+
params = XorParams(n_points_per_quadrant=20, seed=42)
|
|
91
|
+
|
|
92
|
+
result1 = XorGenerator.generate(params)
|
|
93
|
+
result2 = XorGenerator.generate(params)
|
|
94
|
+
|
|
95
|
+
np.testing.assert_array_equal(result1["X_full"], result2["X_full"])
|
|
96
|
+
np.testing.assert_array_equal(result1["y_full"], result2["y_full"])
|
|
97
|
+
|
|
98
|
+
def test_generate_different_seeds_different_data(self) -> None:
|
|
99
|
+
"""Different seeds produce different data."""
|
|
100
|
+
params1 = XorParams(n_points_per_quadrant=20, seed=42)
|
|
101
|
+
params2 = XorParams(n_points_per_quadrant=20, seed=123)
|
|
102
|
+
|
|
103
|
+
result1 = XorGenerator.generate(params1)
|
|
104
|
+
result2 = XorGenerator.generate(params2)
|
|
105
|
+
|
|
106
|
+
assert not np.array_equal(result1["X_full"], result2["X_full"])
|
|
107
|
+
|
|
108
|
+
def test_generate_one_hot_labels(self) -> None:
|
|
109
|
+
"""Labels are valid one-hot encodings."""
|
|
110
|
+
params = XorParams(n_points_per_quadrant=10, seed=42)
|
|
111
|
+
result = XorGenerator.generate(params)
|
|
112
|
+
|
|
113
|
+
y_full = result["y_full"]
|
|
114
|
+
row_sums = y_full.sum(axis=1)
|
|
115
|
+
np.testing.assert_array_almost_equal(row_sums, np.ones(len(y_full)))
|
|
116
|
+
|
|
117
|
+
assert set(np.unique(y_full)) == {0.0, 1.0}
|
|
118
|
+
|
|
119
|
+
def test_generate_class_distribution(self) -> None:
|
|
120
|
+
"""Classes are balanced (equal quadrants for each class)."""
|
|
121
|
+
params = XorParams(n_points_per_quadrant=25, seed=42)
|
|
122
|
+
result = XorGenerator.generate(params)
|
|
123
|
+
|
|
124
|
+
y_full = result["y_full"]
|
|
125
|
+
class_0_count = y_full[:, 0].sum()
|
|
126
|
+
class_1_count = y_full[:, 1].sum()
|
|
127
|
+
|
|
128
|
+
assert class_0_count == 50
|
|
129
|
+
assert class_1_count == 50
|
|
130
|
+
|
|
131
|
+
def test_generate_quadrant_distribution(self) -> None:
|
|
132
|
+
"""Points are in correct quadrants based on class."""
|
|
133
|
+
params = XorParams(n_points_per_quadrant=50, margin=0.1, seed=42, shuffle=False, noise=0)
|
|
134
|
+
result = XorGenerator.generate(params)
|
|
135
|
+
|
|
136
|
+
X = result["X_full"]
|
|
137
|
+
y = result["y_full"]
|
|
138
|
+
n = 50
|
|
139
|
+
|
|
140
|
+
q1_x = X[0:n, 0]
|
|
141
|
+
q1_y = X[0:n, 1]
|
|
142
|
+
assert np.all(q1_x > 0)
|
|
143
|
+
assert np.all(q1_y > 0)
|
|
144
|
+
assert np.all(y[0:n, 0] == 1)
|
|
145
|
+
|
|
146
|
+
q2_x = X[n : 2 * n, 0]
|
|
147
|
+
q2_y = X[n : 2 * n, 1]
|
|
148
|
+
assert np.all(q2_x < 0)
|
|
149
|
+
assert np.all(q2_y > 0)
|
|
150
|
+
assert np.all(y[n : 2 * n, 1] == 1)
|
|
151
|
+
|
|
152
|
+
q3_x = X[2 * n : 3 * n, 0]
|
|
153
|
+
q3_y = X[2 * n : 3 * n, 1]
|
|
154
|
+
assert np.all(q3_x < 0)
|
|
155
|
+
assert np.all(q3_y < 0)
|
|
156
|
+
assert np.all(y[2 * n : 3 * n, 0] == 1)
|
|
157
|
+
|
|
158
|
+
q4_x = X[3 * n : 4 * n, 0]
|
|
159
|
+
q4_y = X[3 * n : 4 * n, 1]
|
|
160
|
+
assert np.all(q4_x > 0)
|
|
161
|
+
assert np.all(q4_y < 0)
|
|
162
|
+
assert np.all(y[3 * n : 4 * n, 1] == 1)
|
|
163
|
+
|
|
164
|
+
def test_generate_with_noise(self) -> None:
|
|
165
|
+
"""Noise is applied when specified."""
|
|
166
|
+
params_no_noise = XorParams(n_points_per_quadrant=50, noise=0, seed=42)
|
|
167
|
+
params_with_noise = XorParams(n_points_per_quadrant=50, noise=0.5, seed=42)
|
|
168
|
+
|
|
169
|
+
result_no_noise = XorGenerator.generate(params_no_noise)
|
|
170
|
+
result_with_noise = XorGenerator.generate(params_with_noise)
|
|
171
|
+
|
|
172
|
+
assert not np.array_equal(result_no_noise["X_full"], result_with_noise["X_full"])
|
|
173
|
+
|
|
174
|
+
def test_generate_respects_range(self) -> None:
|
|
175
|
+
"""Points are within specified range (before noise)."""
|
|
176
|
+
params = XorParams(n_points_per_quadrant=100, x_range=2.0, y_range=3.0, margin=0.2, noise=0, seed=42)
|
|
177
|
+
result = XorGenerator.generate(params)
|
|
178
|
+
|
|
179
|
+
X = result["X_full"]
|
|
180
|
+
assert np.all(np.abs(X[:, 0]) <= 2.0)
|
|
181
|
+
assert np.all(np.abs(X[:, 1]) <= 3.0)
|
|
182
|
+
|
|
183
|
+
def test_generate_respects_margin(self) -> None:
|
|
184
|
+
"""Points respect margin around axes."""
|
|
185
|
+
params = XorParams(n_points_per_quadrant=100, margin=0.2, noise=0, seed=42)
|
|
186
|
+
result = XorGenerator.generate(params)
|
|
187
|
+
|
|
188
|
+
X = result["X_full"]
|
|
189
|
+
assert np.all(np.abs(X[:, 0]) >= 0.2)
|
|
190
|
+
assert np.all(np.abs(X[:, 1]) >= 0.2)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@pytest.mark.unit
|
|
194
|
+
@pytest.mark.generators
|
|
195
|
+
class TestXorGetSchema:
|
|
196
|
+
"""Tests for get_schema function."""
|
|
197
|
+
|
|
198
|
+
def test_get_schema_returns_dict(self) -> None:
|
|
199
|
+
"""get_schema returns a dictionary."""
|
|
200
|
+
schema = get_schema()
|
|
201
|
+
assert isinstance(schema, dict)
|
|
202
|
+
|
|
203
|
+
def test_get_schema_has_properties(self) -> None:
|
|
204
|
+
"""Schema has properties field."""
|
|
205
|
+
schema = get_schema()
|
|
206
|
+
assert "properties" in schema
|
|
207
|
+
assert "n_points_per_quadrant" in schema["properties"]
|
|
208
|
+
assert "x_range" in schema["properties"]
|
|
209
|
+
assert "margin" in schema["properties"]
|
|
210
|
+
assert "noise" in schema["properties"]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.mark.unit
|
|
214
|
+
@pytest.mark.generators
|
|
215
|
+
class TestXorVersion:
|
|
216
|
+
"""Tests for version constant."""
|
|
217
|
+
|
|
218
|
+
def test_version_format(self) -> None:
|
|
219
|
+
"""Version follows semver format."""
|
|
220
|
+
parts = VERSION.split(".")
|
|
221
|
+
assert len(parts) == 3
|
|
222
|
+
for part in parts:
|
|
223
|
+
assert part.isdigit()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: juniper-data
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: Dataset generation and management service for the Juniper ecosystem
|
|
5
|
+
Author: Paul Calnon
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy>=1.24.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
13
|
+
Provides-Extra: arc-agi
|
|
14
|
+
Requires-Dist: arc-agi>=0.9.0; extra == "arc-agi"
|
|
15
|
+
Provides-Extra: api
|
|
16
|
+
Requires-Dist: fastapi>=0.100.0; extra == "api"
|
|
17
|
+
Requires-Dist: uvicorn[standard]>=0.23.0; extra == "api"
|
|
18
|
+
Requires-Dist: pydantic-settings>=2.0.0; extra == "api"
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
21
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-timeout>=2.2.0; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
24
|
+
Requires-Dist: pytest-benchmark>=4.0.0; extra == "test"
|
|
25
|
+
Requires-Dist: httpx>=0.24.0; extra == "test"
|
|
26
|
+
Requires-Dist: coverage[toml]>=7.0.0; extra == "test"
|
|
27
|
+
Requires-Dist: juniper-data-client>=0.3.0; extra == "test"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: bandit[sarif]>=1.7.9; extra == "dev"
|
|
32
|
+
Requires-Dist: pip-audit>=2.7.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
34
|
+
Provides-Extra: all
|
|
35
|
+
Requires-Dist: juniper-data[api,arc-agi,dev,test]; extra == "all"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Juniper Data
|
|
39
|
+
|
|
40
|
+
Dataset generation and management service for the Juniper ecosystem.
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
Juniper Data provides a centralized service for generating, storing, and serving datasets used by the Juniper neural network projects. It supports various dataset types including the classic two-spiral classification problem.
|
|
45
|
+
|
|
46
|
+
## Ecosystem Compatibility
|
|
47
|
+
|
|
48
|
+
This service is part of the [Juniper](https://github.com/pcalnon/juniper-ml) ecosystem.
|
|
49
|
+
Verified compatible versions:
|
|
50
|
+
|
|
51
|
+
| juniper-data | juniper-cascor | juniper-canopy | data-client | cascor-client | cascor-worker |
|
|
52
|
+
|---|---|---|---|---|---|
|
|
53
|
+
| 0.4.x | 0.3.x | 0.2.x | >=0.3.1 | >=0.1.0 | >=0.1.0 |
|
|
54
|
+
|
|
55
|
+
For full-stack Docker deployment and integration tests, see [juniper-deploy](https://github.com/pcalnon/juniper-deploy).
|
|
56
|
+
|
|
57
|
+
## Architecture
|
|
58
|
+
|
|
59
|
+
JuniperData is the **foundational data layer** of the Juniper ecosystem. JuniperCascor and juniper-canopy both call JuniperData to generate and retrieve datasets.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
┌─────────────────────┐ REST+WS ┌──────────────────────┐
|
|
63
|
+
│ juniper-canopy │ ◄──────────────► │ JuniperCascor │
|
|
64
|
+
│ Dashboard │ │ Training Svc │
|
|
65
|
+
│ Port 8050 │ │ Port 8200 │
|
|
66
|
+
└──────────┬──────────┘ └──────────┬───────────┘
|
|
67
|
+
│ REST │ REST
|
|
68
|
+
▼ ▼
|
|
69
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
70
|
+
│ JuniperData ◄── (this service) │
|
|
71
|
+
│ Dataset Service · Port 8100 │
|
|
72
|
+
└──────────────────────────────────────────────────────────────┘
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Data contract**: datasets are served as NPZ archives with keys `X_train`, `y_train`, `X_test`, `y_test`, `X_full`, `y_full` (all `float32`).
|
|
76
|
+
|
|
77
|
+
## Related Services
|
|
78
|
+
|
|
79
|
+
| Service | Relationship | Environment Variable |
|
|
80
|
+
|---------|-------------|---------------------|
|
|
81
|
+
| [juniper-cascor](https://github.com/pcalnon/juniper-cascor) | Consumes JuniperData for training datasets | `JUNIPER_DATA_URL=http://localhost:8100` |
|
|
82
|
+
| [juniper-canopy](https://github.com/pcalnon/juniper-canopy) | Consumes JuniperData for visualization data | `JUNIPER_DATA_URL=http://localhost:8100` |
|
|
83
|
+
| [juniper-data-client](https://github.com/pcalnon/juniper-data-client) | PyPI client library for this service | `pip install juniper-data-client` |
|
|
84
|
+
|
|
85
|
+
### Service Configuration
|
|
86
|
+
|
|
87
|
+
| Variable | Default | Description |
|
|
88
|
+
|----------|---------|-------------|
|
|
89
|
+
| `JUNIPER_DATA_HOST` | `0.0.0.0` | Listen address |
|
|
90
|
+
| `JUNIPER_DATA_PORT` | `8100` | Service port |
|
|
91
|
+
| `JUNIPER_DATA_LOG_LEVEL` | `INFO` | Log verbosity |
|
|
92
|
+
|
|
93
|
+
### Docker Deployment
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Full stack with all three services:
|
|
97
|
+
git clone https://github.com/pcalnon/juniper-deploy.git
|
|
98
|
+
cd juniper-deploy && docker compose up --build
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
### Basic Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install -e .
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### With API Support
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install -e ".[api]"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Development Installation
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install -e ".[dev]"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Full Installation
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pip install -e ".[all]"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Quick Start
|
|
128
|
+
|
|
129
|
+
### Generate a Spiral Dataset
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from juniper_data.generators.spiral import SpiralGenerator
|
|
133
|
+
|
|
134
|
+
generator = SpiralGenerator()
|
|
135
|
+
dataset = generator.generate(n_points=100, n_spirals=2, noise=0.1)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Start the API Server
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uvicorn juniper_data.api.app:app --reload
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## API Endpoints
|
|
145
|
+
|
|
146
|
+
| Endpoint | Method | Description |
|
|
147
|
+
| ------------------------------- | ------ | ---------------------------------- |
|
|
148
|
+
| `/v1/health` | GET | Health check endpoint |
|
|
149
|
+
| `/v1/datasets` | GET | List available datasets |
|
|
150
|
+
| `/v1/datasets/{id}` | GET | Get a specific dataset |
|
|
151
|
+
| `/v1/generators/spiral` | POST | Generate a new spiral dataset |
|
|
152
|
+
| `/v1/generators/spiral/config` | GET | Get spiral generator configuration |
|
|
153
|
+
|
|
154
|
+
## Project Structure
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
JuniperData/
|
|
158
|
+
├── juniper_data/
|
|
159
|
+
│ ├── core/ # Core functionality and base classes
|
|
160
|
+
│ ├── generators/ # Dataset generators
|
|
161
|
+
│ │ └── spiral/ # Spiral dataset generator
|
|
162
|
+
│ ├── storage/ # Dataset persistence layer
|
|
163
|
+
│ └── api/ # FastAPI application
|
|
164
|
+
│ └── routes/ # API route handlers
|
|
165
|
+
├── tests/
|
|
166
|
+
│ ├── unit/ # Unit tests
|
|
167
|
+
│ └── integration/ # Integration tests
|
|
168
|
+
├── pyproject.toml # Project configuration
|
|
169
|
+
└── README.md # This file
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
### Running Tests
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Running Tests with Coverage
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pytest --cov=juniper_data --cov-report=html
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Code Formatting
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
black juniper_data tests
|
|
190
|
+
isort juniper_data tests
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Type Checking
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
mypy juniper_data
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Juniper Ecosystem
|
|
200
|
+
|
|
201
|
+
| Repository | Description |
|
|
202
|
+
|-----------|-------------|
|
|
203
|
+
| [juniper-data](https://github.com/pcalnon/juniper-data) | Dataset generation service (this repo) |
|
|
204
|
+
| [juniper-cascor](https://github.com/pcalnon/juniper-cascor) | CasCor neural network training service |
|
|
205
|
+
| [juniper-canopy](https://github.com/pcalnon/juniper-canopy) | Real-time monitoring dashboard |
|
|
206
|
+
| [juniper-data-client](https://github.com/pcalnon/juniper-data-client) | PyPI: `juniper-data-client` |
|
|
207
|
+
| [juniper-cascor-client](https://github.com/pcalnon/juniper-cascor-client) | PyPI: `juniper-cascor-client` |
|
|
208
|
+
| [juniper-cascor-worker](https://github.com/pcalnon/juniper-cascor-worker) | PyPI: `juniper-cascor-worker` |
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT License - Copyright (c) 2024-2026 Paul Calnon
|
|
213
|
+
|
|
214
|
+
## Git Leaks
|
|
215
|
+
|
|
216
|
+

|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
juniper_data/__init__.py,sha256=f-YIxV_v8g_ZCO3-NouWqAvBXUBQRlqZGV0iSH-a0MQ,2744
|
|
2
|
+
juniper_data/__main__.py,sha256=aZ5Z2vdYmBVMgS_PO2iVGbDr5kHDrTZDt0kc1Yw0XY0,2005
|
|
3
|
+
juniper_data/api/__init__.py,sha256=ivCMO5TtsakyWGfJG0Uvvwh1S4atQbYb23FZPevYZSc,186
|
|
4
|
+
juniper_data/api/app.py,sha256=Vc_DLVUq4Qvab6x0S6N1X2orxXZC4RxYCAOHyQ8nCr0,3642
|
|
5
|
+
juniper_data/api/middleware.py,sha256=jnZ2L7qWOTlxTFps9-QtOonX_7jhKGhKkt1mriRWMwI,2830
|
|
6
|
+
juniper_data/api/security.py,sha256=Hd3pmGNlmbnqvRcbTm78Ow65XtUZK2RuXZunBL1ebCc,7269
|
|
7
|
+
juniper_data/api/settings.py,sha256=Ek90KdHDa7zv9imNCLrm1U6YaSQ1b9eNxZCk0wB__jk,4839
|
|
8
|
+
juniper_data/api/routes/__init__.py,sha256=Zun__tMjPacvwVLfEyFbCMm2BqsOXMvMScifKqpx_xc,132
|
|
9
|
+
juniper_data/api/routes/datasets.py,sha256=pRY0fAM8LZ20ITz49Waon4sjZuSJkB9RaM1JddKNvcI,12587
|
|
10
|
+
juniper_data/api/routes/generators.py,sha256=Ix6m7sgPRsHuBVrZfa3IDxU2IhQi1_HJ5TlH9BwT6JQ,5115
|
|
11
|
+
juniper_data/api/routes/health.py,sha256=9Z8QyKcO3SnNzR1_0oMyVXH09-uZBJjLCQWgNqA7r3E,1440
|
|
12
|
+
juniper_data/core/__init__.py,sha256=3zvR-Dmj3jJNoCwnZ5F9els42U7eIS5y_E43vrMy-DY,787
|
|
13
|
+
juniper_data/core/artifacts.py,sha256=ldxICuQA0wVG-_NMStfk5pLZ16TTsV_0AyKowkZ1T1o,1830
|
|
14
|
+
juniper_data/core/dataset_id.py,sha256=5bR8Nm0S8NpKixRLYTnYYeGU2sn3RO1mQZ4DdRJW0eg,1197
|
|
15
|
+
juniper_data/core/models.py,sha256=wwIw77Tb6BFV1i10zf5MvqJMy9bmovh-D41WxyF0WLw,3392
|
|
16
|
+
juniper_data/core/split.py,sha256=cL7_WItaLqFshyuc48GX49jYxUmTKTuNuoF2I_4R0D4,3828
|
|
17
|
+
juniper_data/generators/__init__.py,sha256=lyPQAXGolK4K5xUlefV5blznHtiuqBamUIQ6DZYX_xw,419
|
|
18
|
+
juniper_data/generators/arc_agi/__init__.py,sha256=cgrwth7zfJPUlIrr0sGmvWb9VCXrHE8SKxKXOYzQyiU,315
|
|
19
|
+
juniper_data/generators/arc_agi/generator.py,sha256=6mdq_E_uYZ0lPhUyWxRRG1ptyPKa2cb2Ajh-TW3usOs,8013
|
|
20
|
+
juniper_data/generators/arc_agi/params.py,sha256=Xk2CoZB1fLF74IvzKoCF-V4Cf1x_d-fm1ipWMdJKBc8,1996
|
|
21
|
+
juniper_data/generators/checkerboard/__init__.py,sha256=4o8qcmkylkX0sUTUsSvBaEn8KcObxEQamutFQPUVlo4,351
|
|
22
|
+
juniper_data/generators/checkerboard/generator.py,sha256=-gHa4PI8S_xUsGM5D0TsX5sBqpbKNw_T-G2ScQobvAg,3610
|
|
23
|
+
juniper_data/generators/checkerboard/params.py,sha256=9hp_wQLTd7Qpz1tX-bhIVO1gslNFnbAzgOs2HLrkVBU,1292
|
|
24
|
+
juniper_data/generators/circles/__init__.py,sha256=bqsMH10l0P8_0OwczX9Yr7W5VBSk-KmVtYvhLu0Z1us,310
|
|
25
|
+
juniper_data/generators/circles/generator.py,sha256=xjH9IQlo9xAanznnA16X_g9F1yEzjItZyREWVWHycE0,3664
|
|
26
|
+
juniper_data/generators/circles/params.py,sha256=N9HzOuqBTrNsic9KMWFXRJBeqI8w66xMD08lh08mvxY,1342
|
|
27
|
+
juniper_data/generators/csv_import/__init__.py,sha256=0G8cSCWNJT16spZFXJ1GQ_4bMqtUbStS6rlc6knmIJg,335
|
|
28
|
+
juniper_data/generators/csv_import/generator.py,sha256=co-wbahAkYOyF4QauqtEezdALoHrAywSDI0b8Ri4m9o,6966
|
|
29
|
+
juniper_data/generators/csv_import/params.py,sha256=xODc4H1k9qMt-RsbTCpwG11EVcyRrW0UVcTkcVfGFsk,1627
|
|
30
|
+
juniper_data/generators/gaussian/__init__.py,sha256=CcAon3pYzU7hwqYPFa8y6kDY9qqrAcTDVRQtuymwF4w,312
|
|
31
|
+
juniper_data/generators/gaussian/generator.py,sha256=OS-7qQDBLdAKcfNYwvGCiL5nxiXr5nKQ1AiaM1ib8uQ,5362
|
|
32
|
+
juniper_data/generators/gaussian/params.py,sha256=RcDuYVcIbtGn7GAWxkoVypSIINeyNAEO-W9-C03TjDE,2356
|
|
33
|
+
juniper_data/generators/mnist/__init__.py,sha256=Ax5fi21Eh0ykN_vMu9Lzj_aLDi6fb4y5M3G5dRuBqYM,288
|
|
34
|
+
juniper_data/generators/mnist/generator.py,sha256=exNfvoS5iMIKOi3axWJbbsG3Rbb35dmvwQq0fmJDK9U,3807
|
|
35
|
+
juniper_data/generators/mnist/params.py,sha256=cheR4V5Gvp9ABXhDlfxYEb01Wr466CSHcvcbW5DmSbA,1351
|
|
36
|
+
juniper_data/generators/spiral/__init__.py,sha256=RK02ztKeqWon-ILhyH4SjeAtz9PCZDcrsBpO4wVoc50,1365
|
|
37
|
+
juniper_data/generators/spiral/defaults.py,sha256=OPeixDRXh2X7NKUPUyzlml0qYtj7DrqDG3t18NbFZyo,1077
|
|
38
|
+
juniper_data/generators/spiral/generator.py,sha256=KYDV6cd9YoFCOaJURRJd_yJplG-TghswK8OTLaBe0cw,7100
|
|
39
|
+
juniper_data/generators/spiral/params.py,sha256=cch0nca0of-rvxzdSn91khGmNbPa7bGgfosW03IJ13E,4906
|
|
40
|
+
juniper_data/generators/xor/__init__.py,sha256=Sf_QCNFGpVmaAIFDDFuSYEje7hmsR9lWbfWSxrp7fwI,271
|
|
41
|
+
juniper_data/generators/xor/generator.py,sha256=HGfddoDPzwFUxHsmSJEPht42y-9RbNtQeY178tbrnbg,4832
|
|
42
|
+
juniper_data/generators/xor/params.py,sha256=6Pta8M1NlF_6J4hLNl2WmnmV7eakjnjOFHPVC9d2xQQ,1379
|
|
43
|
+
juniper_data/storage/__init__.py,sha256=tCaD9PfCAVWRRgH975S_VSG9hvE0t0xcXhKDRoiGEz4,3658
|
|
44
|
+
juniper_data/storage/base.py,sha256=cbkPtOSBZqn1gq3RSz0Amg9A6NJaADIITp8W5Hjmr7Y,8608
|
|
45
|
+
juniper_data/storage/cached.py,sha256=bo94ltUI8HDx4pEXq0uQUsBXWcSsxko-syyDrVgybS8,6674
|
|
46
|
+
juniper_data/storage/hf_store.py,sha256=D_7OYBakcVBgBYVC7d8-JC3BoxIHxanxyiS1xY42neg,8515
|
|
47
|
+
juniper_data/storage/kaggle_store.py,sha256=rIQ-1k2Dr5QcpO64eed6NzKVTVWVHUXbidH0bszztQ8,10799
|
|
48
|
+
juniper_data/storage/local_fs.py,sha256=s2biQkjoWuKARz7P3pUa6-S6gBKFdRCikJuRNbqZVwA,7366
|
|
49
|
+
juniper_data/storage/memory.py,sha256=_UKrqG20kXGscHQw_IhWpBBa5Q4l589tfeCYQXCgXa8,4059
|
|
50
|
+
juniper_data/storage/postgres_store.py,sha256=hyHKzc16JtiFr__lRAyLFNnZ4GWbNc1O5BBXtTZONmo,13416
|
|
51
|
+
juniper_data/storage/redis_store.py,sha256=gjs05DHxT3sNyhD1ZjBy--xuwVrd2bP17Mq97iPl-W0,8011
|
|
52
|
+
juniper_data/tests/__init__.py,sha256=w63Rss-gcq9r_60LPWJkbZTUUOLfQlgqjZYTX-jT0Xg,35
|
|
53
|
+
juniper_data/tests/conftest.py,sha256=Y44G_fBs7hdypuXVE1TVVf_VRReAW9BBiG8_p8x8MYY,1909
|
|
54
|
+
juniper_data/tests/fixtures/generate_golden_datasets.py,sha256=pL54BRaG5xnqu5GbC7A5r5e2uzSE5yIas7YYPzJLIQw,6497
|
|
55
|
+
juniper_data/tests/integration/__init__.py,sha256=s-CLb60cwfh53nHtCf7mjmtAJ_Z3duzYT7g-UhjQS5Y,42
|
|
56
|
+
juniper_data/tests/integration/test_api.py,sha256=UbXgGmAps7mg43e8FqOBLfhJ7AP93jU4a2PpKB8bft4,10086
|
|
57
|
+
juniper_data/tests/integration/test_e2e_workflow.py,sha256=8ad9qig5q0Aka5Y8otJGXxA8Q2zGUclzDM_GxisBlUg,15360
|
|
58
|
+
juniper_data/tests/integration/test_lifecycle_api.py,sha256=kfJ6clcrMdhQEzAi1TH5wEEBTG9hz5IFEYI4V8I7UmI,11606
|
|
59
|
+
juniper_data/tests/integration/test_security_integration.py,sha256=2JNiTRENjkxCyiprsO5Q6tsgkGlbVK6CrGdxzkP0wLg,7186
|
|
60
|
+
juniper_data/tests/integration/test_storage_workflow.py,sha256=Oc0XvMdGRBxG2KiM6-K2jOvUeF92evpvbikwqY3StUc,10156
|
|
61
|
+
juniper_data/tests/performance/__init__.py,sha256=PqIqrY081KlNAPL4l014W5cYlEpD-iTp0JGRr4GuD1w,52
|
|
62
|
+
juniper_data/tests/performance/test_generator_benchmarks.py,sha256=t7z60VUJjuO-Srdpxh6n3VX4RW8CduDSOcb7tHmXInk,8718
|
|
63
|
+
juniper_data/tests/performance/test_storage_benchmarks.py,sha256=JqzCYOiiiwR5jB5P4nVpSnwEKsMrDrKrsHunN8sey0w,11140
|
|
64
|
+
juniper_data/tests/unit/__init__.py,sha256=zNL9fUJO-udWbVm128WjFl33SGlFV8Z9--9w5wa45F0,35
|
|
65
|
+
juniper_data/tests/unit/test_api_app.py,sha256=IgSRws5U9hE4ATcuvewT4WcbKX5M85G_NsSryuonpac,8139
|
|
66
|
+
juniper_data/tests/unit/test_api_routes.py,sha256=q1rSREG5duNfNRYNpfH_2j18187PtBcvUL4Nnuh_kks,15608
|
|
67
|
+
juniper_data/tests/unit/test_api_settings.py,sha256=zRITgvV3R2loPQDcyLxm3hkEsj7T28rGX1eB3X0ZD7w,3197
|
|
68
|
+
juniper_data/tests/unit/test_arc_agi_generator.py,sha256=9YwAG9EoU57oQFTCGY9cor2QihUTpOscGfSonMfpQXY,19238
|
|
69
|
+
juniper_data/tests/unit/test_artifacts.py,sha256=lmxY042dJ2yr5QIILtC5QpcZa4mb37FO46qEz3GtUtU,4928
|
|
70
|
+
juniper_data/tests/unit/test_cached_store.py,sha256=3YL4SWN_VEme7QfQYMJ39D4QNdCm5UMiI6Kn0pbwY2U,14830
|
|
71
|
+
juniper_data/tests/unit/test_checkerboard_generator.py,sha256=qbM0EHHRcTDQL9aMrme33Pqz8G4k6g1riVx0VcV8ExU,8056
|
|
72
|
+
juniper_data/tests/unit/test_circles_generator.py,sha256=O06rbL_k_gEUqBGAuzlphci4AWdpQnzkrbtB402crbQ,8971
|
|
73
|
+
juniper_data/tests/unit/test_csv_import_generator.py,sha256=Zt2MUkbNT5zR2EoMGZevlYjzYSEuQR4F-e99cCBfPcw,12279
|
|
74
|
+
juniper_data/tests/unit/test_dataset_id.py,sha256=rdFiX4VOx9Dc-s8dW9qTV17XS2RBmz3mCmhUH02IEFA,5917
|
|
75
|
+
juniper_data/tests/unit/test_gaussian_generator.py,sha256=etTAFt-0l_PvbY3xNCXewK9cvwmL_boz9X0Z-vSOaHI,11849
|
|
76
|
+
juniper_data/tests/unit/test_hf_store.py,sha256=pKEJOj5S_DW-ZWxrIjGO5g0cvUsaCnSU0Ex3e1oWPic,16481
|
|
77
|
+
juniper_data/tests/unit/test_init.py,sha256=AC0hS76V8pQxIbANcnisYOhoWxS28YW4X0E7OPQ1t94,4099
|
|
78
|
+
juniper_data/tests/unit/test_kaggle_store.py,sha256=YDthyg1oUR42usi7_I7uTDnMXmhk2nfVf55gAb6dz_8,20524
|
|
79
|
+
juniper_data/tests/unit/test_lifecycle.py,sha256=EIQzO0sk3as8uQL_D1PHU0n13qQv-183ZLJDn2JBzWI,15504
|
|
80
|
+
juniper_data/tests/unit/test_main.py,sha256=e_NC71xbgdu9xGRameLNzYrrQV4l1PuBsfxDszsw7Jo,5565
|
|
81
|
+
juniper_data/tests/unit/test_middleware.py,sha256=8qhPzzEhnmEeS9gpgzboPSjpR3Jb_310Lop0HA2C1tg,2863
|
|
82
|
+
juniper_data/tests/unit/test_mnist_generator.py,sha256=mNRnt7sYJStNHJXuauyAKtKQ0Hf3VDqgAtrqKU3eMRI,13294
|
|
83
|
+
juniper_data/tests/unit/test_postgres_store.py,sha256=vxOiSqfAfgUEBdvKJpjx_qVDQ6hXe7NjhoxKawm8EFM,18675
|
|
84
|
+
juniper_data/tests/unit/test_redis_store.py,sha256=6oailneNwaUTTRhl5-kLgN5WDlxCjDknWnrk6Dhw3kU,17807
|
|
85
|
+
juniper_data/tests/unit/test_security.py,sha256=tHlLXUFQHqkf1lQcoQILYjMBMqy4A97UtTaktTlACFE,9797
|
|
86
|
+
juniper_data/tests/unit/test_security_boundaries.py,sha256=_QiPPIQR7f9a-gie3xwk57CdLrQ1HqRZUQ7bG5QzQqI,23355
|
|
87
|
+
juniper_data/tests/unit/test_spiral_generator.py,sha256=zKcRsV5h5hKavUA7yNFeTJpk8_0eXYJg8nr4OB4icyw,21145
|
|
88
|
+
juniper_data/tests/unit/test_split.py,sha256=6kYLZHRjV59_LKPJoyzdW37Gxtz2cfexCO7745q5JTc,9409
|
|
89
|
+
juniper_data/tests/unit/test_storage.py,sha256=gVV6uFGwV_nUh7LmQ8b4B0eH8RbX-I7w-ZK7Oz_SQBM,29626
|
|
90
|
+
juniper_data/tests/unit/test_xor_generator.py,sha256=GY0N6Iq0SCXrWPBob3Ob-Cf9oOdHhR26C80Uz_o1ysA,7689
|
|
91
|
+
juniper_data-0.4.2.dist-info/licenses/LICENSE,sha256=gS6kn2rddVUTz-UTtrOqHcqE5w4qYpt7gmKcLG_RxZ4,1091
|
|
92
|
+
juniper_data-0.4.2.dist-info/METADATA,sha256=PKr6RSoWSigVe9N62q7-YlMIW2NSr47MN_H55CaCcgI,7679
|
|
93
|
+
juniper_data-0.4.2.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
94
|
+
juniper_data-0.4.2.dist-info/top_level.txt,sha256=Mdnkl3i3xziR8cu3cMfNrKBAIACp1RB6e1Ks5MWHXa0,13
|
|
95
|
+
juniper_data-0.4.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Juniper Canopy project
|
|
2
|
+
|
|
3
|
+
Copyright 2024, 2025 Paul Calnon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
juniper_data
|