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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Python SDK for calling the ProxyML API
5
5
  Author-email: ProxyML <contact@proxyml.ai>
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proxyml"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "Python SDK for calling the ProxyML API"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Python SDK for calling the ProxyML API
5
5
  Author-email: ProxyML <contact@proxyml.ai>
6
6
  License: Apache License
@@ -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 test_put_schema_success(mock_put):
145
- mock_put.return_value = _mock_response(200, {"status": "ok"})
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 == {"status": "ok"}
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.assert_called_once_with(
195
- endpoint="/synthesize/neighbors", payload={"n": 2}
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