proxyml 0.1.4__tar.gz → 0.1.5__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.
- {proxyml-0.1.4 → proxyml-0.1.5}/PKG-INFO +1 -1
- {proxyml-0.1.4 → proxyml-0.1.5}/pyproject.toml +1 -1
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml/__init__.py +6 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml/client.py +50 -6
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml.egg-info/PKG-INFO +1 -1
- {proxyml-0.1.4 → proxyml-0.1.5}/tests/test_client.py +90 -7
- {proxyml-0.1.4 → proxyml-0.1.5}/LICENSE +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/README.md +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/setup.cfg +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml/schema.py +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml.egg-info/requires.txt +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.1.4 → proxyml-0.1.5}/tests/test_schema.py +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from proxyml.client import (
|
|
2
2
|
put_schema,
|
|
3
|
+
fetch_schema,
|
|
4
|
+
list_schemas,
|
|
5
|
+
delete_schema,
|
|
3
6
|
synthesize_data,
|
|
4
7
|
train_surrogate,
|
|
5
8
|
predict,
|
|
@@ -24,6 +27,9 @@ from proxyml.schema import (
|
|
|
24
27
|
|
|
25
28
|
__all__ = [
|
|
26
29
|
"put_schema",
|
|
30
|
+
"fetch_schema",
|
|
31
|
+
"list_schemas",
|
|
32
|
+
"delete_schema",
|
|
27
33
|
"synthesize_data",
|
|
28
34
|
"train_surrogate",
|
|
29
35
|
"predict",
|
|
@@ -61,10 +61,10 @@ def delete(endpoint: str) -> requests.models.Response:
|
|
|
61
61
|
return r
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def put_schema(schema: dict):
|
|
65
|
-
r = put(endpoint='/schema', payload=schema)
|
|
64
|
+
def put_schema(schema: dict, name: str = "default"):
|
|
65
|
+
r = put(endpoint=f'/schema/{name}', payload=schema)
|
|
66
66
|
if r.status_code == 200:
|
|
67
|
-
logger.info("Schema uploaded successfully")
|
|
67
|
+
logger.info("Schema '%s' uploaded successfully", name)
|
|
68
68
|
return r.json()
|
|
69
69
|
logger.error(
|
|
70
70
|
"Schema upload failed with status %s: %s",
|
|
@@ -74,6 +74,48 @@ def put_schema(schema: dict):
|
|
|
74
74
|
return None
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def fetch_schema(name: str = "default") -> dict | None:
|
|
78
|
+
"""Retrieve a stored feature schema by name."""
|
|
79
|
+
r = get(endpoint=f'/schema/{name}', params={})
|
|
80
|
+
if r.status_code == 200:
|
|
81
|
+
return r.json()
|
|
82
|
+
logger.error(
|
|
83
|
+
"Fetch schema failed with status %s: %s",
|
|
84
|
+
r.status_code,
|
|
85
|
+
r.text,
|
|
86
|
+
)
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def list_schemas() -> list[dict] | None:
|
|
91
|
+
"""Return all named schemas for the authenticated user."""
|
|
92
|
+
r = get(endpoint='/schemas', params={})
|
|
93
|
+
if r.status_code == 200:
|
|
94
|
+
return r.json()['schemas']
|
|
95
|
+
logger.error(
|
|
96
|
+
"List schemas failed with status %s: %s",
|
|
97
|
+
r.status_code,
|
|
98
|
+
r.text,
|
|
99
|
+
)
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def delete_schema(name: str) -> bool:
|
|
104
|
+
"""Delete a named schema. Returns True on success, False if not found."""
|
|
105
|
+
r = delete(endpoint=f'/schema/{name}')
|
|
106
|
+
if r.status_code == 204:
|
|
107
|
+
return True
|
|
108
|
+
if r.status_code == 404:
|
|
109
|
+
logger.warning("Schema '%s' not found", name)
|
|
110
|
+
return False
|
|
111
|
+
logger.error(
|
|
112
|
+
"Delete schema failed with status %s: %s",
|
|
113
|
+
r.status_code,
|
|
114
|
+
r.text,
|
|
115
|
+
)
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
77
119
|
def _cast_column(series: pd.Series, ftype: str) -> pd.Series:
|
|
78
120
|
if ftype in ("continuous",):
|
|
79
121
|
return series.astype(float)
|
|
@@ -87,11 +129,11 @@ def _cast_column(series: pd.Series, ftype: str) -> pd.Series:
|
|
|
87
129
|
return series
|
|
88
130
|
|
|
89
131
|
|
|
90
|
-
def synthesize_data(num_points: int = 100, sample: list | None = None, as_df: bool = True):
|
|
132
|
+
def synthesize_data(num_points: int = 100, sample: list | None = None, as_df: bool = True, schema_name: str = "default"):
|
|
91
133
|
if sample is None:
|
|
92
|
-
r = post(endpoint='/synthesize/neighbors', payload={'n': num_points})
|
|
134
|
+
r = post(endpoint='/synthesize/neighbors', payload={'n': num_points, 'schema_name': schema_name})
|
|
93
135
|
else:
|
|
94
|
-
r = post(endpoint='/synthesize/blended', payload={'n': num_points, 'instance': [col.item() for col in sample]})
|
|
136
|
+
r = post(endpoint='/synthesize/blended', payload={'n': num_points, 'instance': [col.item() for col in sample], 'schema_name': schema_name})
|
|
95
137
|
if r.status_code == 200:
|
|
96
138
|
payload = r.json()
|
|
97
139
|
if as_df:
|
|
@@ -114,6 +156,7 @@ def train_surrogate(
|
|
|
114
156
|
feature_names: list[str] | None,
|
|
115
157
|
task: str = 'auto',
|
|
116
158
|
test_size: float = 0.2,
|
|
159
|
+
schema_name: str = "default",
|
|
117
160
|
name: str | None = None,
|
|
118
161
|
comments: str | None = None,
|
|
119
162
|
):
|
|
@@ -123,6 +166,7 @@ def train_surrogate(
|
|
|
123
166
|
'feature_names': feature_names,
|
|
124
167
|
'task': task,
|
|
125
168
|
'test_size': test_size,
|
|
169
|
+
'schema_name': schema_name,
|
|
126
170
|
}
|
|
127
171
|
if name is not None:
|
|
128
172
|
payload['name'] = name
|
|
@@ -8,12 +8,15 @@ from proxyml.client import (
|
|
|
8
8
|
_cast_column,
|
|
9
9
|
_headers,
|
|
10
10
|
delete_model,
|
|
11
|
+
delete_schema,
|
|
11
12
|
diff_models,
|
|
13
|
+
fetch_schema,
|
|
12
14
|
find_counterfactuals,
|
|
13
15
|
get_model_summary,
|
|
14
16
|
get_usage,
|
|
15
17
|
interpret_counterfactual,
|
|
16
18
|
list_models,
|
|
19
|
+
list_schemas,
|
|
17
20
|
predict,
|
|
18
21
|
predict_batch,
|
|
19
22
|
put_schema,
|
|
@@ -141,11 +144,18 @@ def _mock_response(status_code, json_body):
|
|
|
141
144
|
|
|
142
145
|
|
|
143
146
|
@patch("proxyml.client.put")
|
|
144
|
-
def
|
|
145
|
-
mock_put.return_value = _mock_response(200, {"
|
|
147
|
+
def test_put_schema_default(mock_put):
|
|
148
|
+
mock_put.return_value = _mock_response(200, {"features": []})
|
|
146
149
|
result = put_schema({"features": []})
|
|
147
|
-
assert result == {"
|
|
148
|
-
mock_put.assert_called_once_with(endpoint="/schema", payload={"features": []})
|
|
150
|
+
assert result == {"features": []}
|
|
151
|
+
mock_put.assert_called_once_with(endpoint="/schema/default", payload={"features": []})
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@patch("proxyml.client.put")
|
|
155
|
+
def test_put_schema_named(mock_put):
|
|
156
|
+
mock_put.return_value = _mock_response(200, {"features": []})
|
|
157
|
+
put_schema({"features": []}, name="credit")
|
|
158
|
+
mock_put.assert_called_once_with(endpoint="/schema/credit", payload={"features": []})
|
|
149
159
|
|
|
150
160
|
|
|
151
161
|
@patch("proxyml.client.put")
|
|
@@ -155,6 +165,60 @@ def test_put_schema_failure(mock_put):
|
|
|
155
165
|
assert result is None
|
|
156
166
|
|
|
157
167
|
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
# fetch_schema / list_schemas / delete_schema
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
@patch("proxyml.client.get")
|
|
173
|
+
def test_fetch_schema_default(mock_get):
|
|
174
|
+
mock_get.return_value = _mock_response(200, {"features": [{"type": "continuous", "name": "age"}]})
|
|
175
|
+
result = fetch_schema()
|
|
176
|
+
assert result["features"][0]["name"] == "age"
|
|
177
|
+
mock_get.assert_called_once_with(endpoint="/schema/default", params={})
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@patch("proxyml.client.get")
|
|
181
|
+
def test_fetch_schema_named(mock_get):
|
|
182
|
+
mock_get.return_value = _mock_response(200, {"features": []})
|
|
183
|
+
fetch_schema(name="credit")
|
|
184
|
+
mock_get.assert_called_once_with(endpoint="/schema/credit", params={})
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@patch("proxyml.client.get")
|
|
188
|
+
def test_fetch_schema_not_found_returns_none(mock_get):
|
|
189
|
+
mock_get.return_value = _mock_response(404, {"detail": "not found"})
|
|
190
|
+
assert fetch_schema("missing") is None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@patch("proxyml.client.get")
|
|
194
|
+
def test_list_schemas_success(mock_get):
|
|
195
|
+
schemas = [{"name": "default", "updated_at": "2026-04-22T10:00:00"},
|
|
196
|
+
{"name": "credit", "updated_at": "2026-04-22T11:00:00"}]
|
|
197
|
+
mock_get.return_value = _mock_response(200, {"schemas": schemas})
|
|
198
|
+
result = list_schemas()
|
|
199
|
+
assert result == schemas
|
|
200
|
+
mock_get.assert_called_once_with(endpoint="/schemas", params={})
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@patch("proxyml.client.get")
|
|
204
|
+
def test_list_schemas_failure_returns_none(mock_get):
|
|
205
|
+
mock_get.return_value = _mock_response(401, {"detail": "unauthorized"})
|
|
206
|
+
assert list_schemas() is None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@patch("proxyml.client.delete")
|
|
210
|
+
def test_delete_schema_success(mock_del):
|
|
211
|
+
mock_del.return_value = _mock_response(204, None)
|
|
212
|
+
assert delete_schema("credit") is True
|
|
213
|
+
mock_del.assert_called_once_with(endpoint="/schema/credit")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@patch("proxyml.client.delete")
|
|
217
|
+
def test_delete_schema_not_found(mock_del):
|
|
218
|
+
mock_del.return_value = _mock_response(404, {"detail": "not found"})
|
|
219
|
+
assert delete_schema("no-such-schema") is False
|
|
220
|
+
|
|
221
|
+
|
|
158
222
|
@patch("proxyml.client.post")
|
|
159
223
|
def test_predict_success(mock_post):
|
|
160
224
|
mock_post.return_value = _mock_response(200, {"prediction": 1, "probability": 0.9})
|
|
@@ -191,9 +255,19 @@ def test_synthesize_data_no_sample(mock_post):
|
|
|
191
255
|
df = synthesize_data(num_points=2, sample=None)
|
|
192
256
|
assert isinstance(df, pd.DataFrame)
|
|
193
257
|
assert list(df.columns) == ["f_cont", "f_cat"]
|
|
194
|
-
mock_post.
|
|
195
|
-
|
|
196
|
-
|
|
258
|
+
payload = mock_post.call_args.kwargs["payload"]
|
|
259
|
+
assert payload["n"] == 2
|
|
260
|
+
assert payload["schema_name"] == "default"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@patch("proxyml.client.post")
|
|
264
|
+
def test_synthesize_data_named_schema(mock_post):
|
|
265
|
+
mock_post.return_value = _mock_response(200, {
|
|
266
|
+
"samples": [[1.0]], "feature_names": ["x"], "feature_types": ["continuous"],
|
|
267
|
+
})
|
|
268
|
+
synthesize_data(num_points=1, schema_name="credit")
|
|
269
|
+
payload = mock_post.call_args.kwargs["payload"]
|
|
270
|
+
assert payload["schema_name"] == "credit"
|
|
197
271
|
|
|
198
272
|
|
|
199
273
|
@patch("proxyml.client.post")
|
|
@@ -217,10 +291,19 @@ def test_train_surrogate_with_metadata(mock_post):
|
|
|
217
291
|
payload = mock_post.call_args.kwargs["payload"]
|
|
218
292
|
assert payload["name"] == "v1"
|
|
219
293
|
assert payload["comments"] == "test run"
|
|
294
|
+
assert payload["schema_name"] == "default"
|
|
220
295
|
assert result["version"] == "abc-123"
|
|
221
296
|
assert result["trained_at"] == "2026-04-19T12:00:00"
|
|
222
297
|
|
|
223
298
|
|
|
299
|
+
@patch("proxyml.client.post")
|
|
300
|
+
def test_train_surrogate_named_schema(mock_post):
|
|
301
|
+
mock_post.return_value = _mock_response(200, {"version": "abc-123"})
|
|
302
|
+
train_surrogate(samples=[[1.0]], predictions=[1.0], feature_names=None, schema_name="credit")
|
|
303
|
+
payload = mock_post.call_args.kwargs["payload"]
|
|
304
|
+
assert payload["schema_name"] == "credit"
|
|
305
|
+
|
|
306
|
+
|
|
224
307
|
@patch("proxyml.client.post")
|
|
225
308
|
def test_train_surrogate_omits_none_metadata(mock_post):
|
|
226
309
|
mock_post.return_value = _mock_response(200, {"version": "abc-123"})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|