proxyml 0.4.0__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.
- {proxyml-0.4.0 → proxyml-0.5.0}/PKG-INFO +2 -2
- {proxyml-0.4.0 → proxyml-0.5.0}/pyproject.toml +2 -2
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml/local/__init__.py +2 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml/local/challenger.py +64 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml.egg-info/PKG-INFO +2 -2
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml.egg-info/requires.txt +1 -1
- {proxyml-0.4.0 → proxyml-0.5.0}/tests/test_local_challenger.py +105 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/LICENSE +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/README.md +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/setup.cfg +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml/__init__.py +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml/client.py +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml/schema_builder.py +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/tests/test_client.py +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/tests/test_dependency_boundaries.py +0 -0
- {proxyml-0.4.0 → proxyml-0.5.0}/tests/test_schema_builder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Python SDK for calling the ProxyML API
|
|
5
5
|
Author-email: ProxyML <contact@proxyml.ai>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Requires-Dist: pandas>=1.5
|
|
|
211
211
|
Requires-Dist: orjson>=3.9
|
|
212
212
|
Requires-Dist: proxyml-core>=0.1
|
|
213
213
|
Provides-Extra: local
|
|
214
|
-
Requires-Dist: proxyml-core[modeling]>=0.2; extra == "local"
|
|
214
|
+
Requires-Dist: proxyml-core[modeling]>=0.2.1; extra == "local"
|
|
215
215
|
Provides-Extra: dev
|
|
216
216
|
Requires-Dist: build; extra == "dev"
|
|
217
217
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proxyml"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Python SDK for calling the ProxyML API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
local = [
|
|
38
|
-
"proxyml-core[modeling]>=0.2",
|
|
38
|
+
"proxyml-core[modeling]>=0.2.1",
|
|
39
39
|
]
|
|
40
40
|
dev = [
|
|
41
41
|
"build",
|
|
@@ -11,6 +11,7 @@ from proxyml.local.challenger import (
|
|
|
11
11
|
Rung,
|
|
12
12
|
TrainedChallenger,
|
|
13
13
|
score_champion,
|
|
14
|
+
to_challenger_upload,
|
|
14
15
|
train_auto_challenger,
|
|
15
16
|
train_challenger,
|
|
16
17
|
)
|
|
@@ -19,6 +20,7 @@ __all__ = [
|
|
|
19
20
|
"train_challenger",
|
|
20
21
|
"train_auto_challenger",
|
|
21
22
|
"score_champion",
|
|
23
|
+
"to_challenger_upload",
|
|
22
24
|
"Complexity",
|
|
23
25
|
"Rung",
|
|
24
26
|
"TrainedChallenger",
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
from dataclasses import dataclass, replace
|
|
14
14
|
from enum import Enum
|
|
15
|
+
from importlib.metadata import version as _pkg_version
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Any, Callable, Literal
|
|
17
18
|
|
|
@@ -60,6 +61,8 @@ def _simple_classifier() -> BaseEstimator:
|
|
|
60
61
|
max_iter=500,
|
|
61
62
|
cv=5,
|
|
62
63
|
n_jobs=-1,
|
|
64
|
+
scoring="accuracy",
|
|
65
|
+
use_legacy_attributes=False,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
|
|
@@ -76,6 +79,8 @@ def _flexible_classifier() -> BaseEstimator:
|
|
|
76
79
|
max_iter=1000,
|
|
77
80
|
cv=5,
|
|
78
81
|
n_jobs=-1,
|
|
82
|
+
scoring="accuracy",
|
|
83
|
+
use_legacy_attributes=False,
|
|
79
84
|
)
|
|
80
85
|
|
|
81
86
|
|
|
@@ -109,6 +114,7 @@ LADDERS: dict[Complexity, Rung] = {
|
|
|
109
114
|
class TrainedChallenger:
|
|
110
115
|
pipeline: Pipeline
|
|
111
116
|
task: Literal["classification", "regression"]
|
|
117
|
+
complexity: Complexity
|
|
112
118
|
metrics: dict[str, float]
|
|
113
119
|
hyperparameters: dict[str, Any]
|
|
114
120
|
export: SurrogateExport
|
|
@@ -192,6 +198,7 @@ def train_challenger(
|
|
|
192
198
|
return TrainedChallenger(
|
|
193
199
|
pipeline=pipeline,
|
|
194
200
|
task=resolved_task,
|
|
201
|
+
complexity=complexity,
|
|
195
202
|
metrics=metrics,
|
|
196
203
|
hyperparameters=hyperparameters,
|
|
197
204
|
export=export,
|
|
@@ -220,6 +227,63 @@ def score_champion(
|
|
|
220
227
|
return score_predictions(np.asarray(labels), np.asarray(predictions), task=task)
|
|
221
228
|
|
|
222
229
|
|
|
230
|
+
def to_challenger_upload(
|
|
231
|
+
result: TrainedChallenger,
|
|
232
|
+
*,
|
|
233
|
+
n_samples: int,
|
|
234
|
+
champion_metrics: dict[str, float] | None = None,
|
|
235
|
+
sdk_version: str | None = None,
|
|
236
|
+
proxyml_core_version: str | None = None,
|
|
237
|
+
) -> dict[str, Any]:
|
|
238
|
+
"""Assemble the JSON-serializable payload for a challenger upload.
|
|
239
|
+
|
|
240
|
+
Matches the shape ProxyML's dashboard/API expects at
|
|
241
|
+
``POST /app/projects/{id}/challenger`` — handles the mechanical assembly
|
|
242
|
+
(serializing the export, stamping SDK/core versions, converting
|
|
243
|
+
``complexity`` to a plain string) so callers don't have to hand-roll it.
|
|
244
|
+
The result is plain ``dict``/``str``/``float`` data, ready for
|
|
245
|
+
``json.dump`` — upload it either by POSTing it directly, or by saving it
|
|
246
|
+
to a file and using the dashboard's "Upload challenger" button.
|
|
247
|
+
|
|
248
|
+
``champion_metrics`` is optional: pass ``None`` (the default) to get a
|
|
249
|
+
self-contained export of the challenger alone — e.g. to save/share it
|
|
250
|
+
before you have a champion to compare against — and fill in
|
|
251
|
+
``champion_metrics`` later. The upload endpoint itself still requires
|
|
252
|
+
``champion_metrics`` at upload time; this function just doesn't force you
|
|
253
|
+
to have it up front.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
result: output of ``train_challenger()``/``train_auto_challenger()``.
|
|
257
|
+
n_samples: size of the evaluation set both ``result.metrics`` and
|
|
258
|
+
``champion_metrics`` were scored on. Not derived automatically —
|
|
259
|
+
``TrainedChallenger`` doesn't retain its internal held-out split,
|
|
260
|
+
and ``champion_metrics`` typically comes from a separate
|
|
261
|
+
``score_champion()`` call the two need to share, so the caller is
|
|
262
|
+
the only one who actually knows this number.
|
|
263
|
+
champion_metrics: the champion's real-world performance, from
|
|
264
|
+
``score_champion()`` — same metric keys as ``result.metrics``.
|
|
265
|
+
Omit if you don't have it yet.
|
|
266
|
+
sdk_version: defaults to the installed ``proxyml`` version.
|
|
267
|
+
proxyml_core_version: defaults to the installed ``proxyml-core`` version.
|
|
268
|
+
"""
|
|
269
|
+
if sdk_version is None:
|
|
270
|
+
sdk_version = _pkg_version("proxyml")
|
|
271
|
+
if proxyml_core_version is None:
|
|
272
|
+
proxyml_core_version = _pkg_version("proxyml-core")
|
|
273
|
+
|
|
274
|
+
payload: dict[str, Any] = {
|
|
275
|
+
"export": result.export.to_dict(),
|
|
276
|
+
"challenger_metrics": result.metrics,
|
|
277
|
+
"n_samples": n_samples,
|
|
278
|
+
"complexity": result.complexity.value,
|
|
279
|
+
"sdk_version": sdk_version,
|
|
280
|
+
"proxyml_core_version": proxyml_core_version,
|
|
281
|
+
}
|
|
282
|
+
if champion_metrics is not None:
|
|
283
|
+
payload["champion_metrics"] = champion_metrics
|
|
284
|
+
return payload
|
|
285
|
+
|
|
286
|
+
|
|
223
287
|
def train_auto_challenger(
|
|
224
288
|
data: str | Path | pd.DataFrame,
|
|
225
289
|
target_col: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Python SDK for calling the ProxyML API
|
|
5
5
|
Author-email: ProxyML <contact@proxyml.ai>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Requires-Dist: pandas>=1.5
|
|
|
211
211
|
Requires-Dist: orjson>=3.9
|
|
212
212
|
Requires-Dist: proxyml-core>=0.1
|
|
213
213
|
Provides-Extra: local
|
|
214
|
-
Requires-Dist: proxyml-core[modeling]>=0.2; extra == "local"
|
|
214
|
+
Requires-Dist: proxyml-core[modeling]>=0.2.1; extra == "local"
|
|
215
215
|
Provides-Extra: dev
|
|
216
216
|
Requires-Dist: build; extra == "dev"
|
|
217
217
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
import pytest
|
|
@@ -7,6 +9,7 @@ from proxyml.local import (
|
|
|
7
9
|
LADDERS,
|
|
8
10
|
TrainedChallenger,
|
|
9
11
|
score_champion,
|
|
12
|
+
to_challenger_upload,
|
|
10
13
|
train_auto_challenger,
|
|
11
14
|
train_challenger,
|
|
12
15
|
)
|
|
@@ -63,6 +66,20 @@ def test_train_challenger_classification():
|
|
|
63
66
|
assert result.export.classes is not None
|
|
64
67
|
|
|
65
68
|
|
|
69
|
+
def test_train_challenger_classification_rungs_fit_without_sklearn_deprecation_warnings():
|
|
70
|
+
# scoring="accuracy" and use_legacy_attributes=False on the SIMPLE/FLEXIBLE
|
|
71
|
+
# classifiers pin today's sklearn defaults explicitly, matching what
|
|
72
|
+
# get_default_classifier() (the MODERATE rung) already does.
|
|
73
|
+
schema = _schema()
|
|
74
|
+
df = _df(seed=12, n=200)
|
|
75
|
+
target = np.where(df["age"] > 50, "senior", "junior")
|
|
76
|
+
|
|
77
|
+
with warnings.catch_warnings():
|
|
78
|
+
warnings.simplefilter("error", FutureWarning)
|
|
79
|
+
train_challenger(df, target, schema, complexity=Complexity.SIMPLE, task="classification")
|
|
80
|
+
train_challenger(df, target, schema, complexity=Complexity.FLEXIBLE, task="classification")
|
|
81
|
+
|
|
82
|
+
|
|
66
83
|
def test_train_challenger_simple_rung_is_more_regularized_than_flexible():
|
|
67
84
|
schema = _schema()
|
|
68
85
|
df = _df(seed=3)
|
|
@@ -181,6 +198,94 @@ def test_score_champion_uses_same_scoring_as_train_challenger():
|
|
|
181
198
|
assert reproduced_metrics["r2"] == pytest.approx(r2_score(target, y_pred))
|
|
182
199
|
|
|
183
200
|
|
|
201
|
+
def test_train_challenger_result_carries_complexity():
|
|
202
|
+
schema = _schema()
|
|
203
|
+
df = _df(seed=13)
|
|
204
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
205
|
+
|
|
206
|
+
result = train_challenger(df, target, schema, complexity=Complexity.FLEXIBLE, task="regression")
|
|
207
|
+
|
|
208
|
+
assert result.complexity is Complexity.FLEXIBLE
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_train_auto_challenger_result_carries_complexity():
|
|
212
|
+
df = _labeled_df(seed=14)
|
|
213
|
+
result = train_auto_challenger(df, "approved", task="classification", complexity=Complexity.SIMPLE)
|
|
214
|
+
|
|
215
|
+
assert result.complexity is Complexity.SIMPLE
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_to_challenger_upload_shape_without_champion_metrics():
|
|
219
|
+
schema = _schema()
|
|
220
|
+
df = _df(seed=15)
|
|
221
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
222
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
|
|
223
|
+
|
|
224
|
+
payload = to_challenger_upload(result, n_samples=40)
|
|
225
|
+
|
|
226
|
+
assert payload["export"] == result.export.to_dict()
|
|
227
|
+
assert payload["challenger_metrics"] == result.metrics
|
|
228
|
+
assert payload["n_samples"] == 40
|
|
229
|
+
assert payload["complexity"] == "moderate"
|
|
230
|
+
assert "champion_metrics" not in payload
|
|
231
|
+
assert isinstance(payload["sdk_version"], str) and payload["sdk_version"]
|
|
232
|
+
assert isinstance(payload["proxyml_core_version"], str) and payload["proxyml_core_version"]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_to_challenger_upload_includes_champion_metrics_when_given():
|
|
236
|
+
schema = _schema()
|
|
237
|
+
df = _df(seed=16)
|
|
238
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
239
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
|
|
240
|
+
champion_metrics = score_champion(target, target, task="regression")
|
|
241
|
+
|
|
242
|
+
payload = to_challenger_upload(result, n_samples=40, champion_metrics=champion_metrics)
|
|
243
|
+
|
|
244
|
+
assert payload["champion_metrics"] == champion_metrics
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_to_challenger_upload_defaults_versions_to_installed_packages():
|
|
248
|
+
from importlib.metadata import version as pkg_version
|
|
249
|
+
|
|
250
|
+
schema = _schema()
|
|
251
|
+
df = _df(seed=17)
|
|
252
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
253
|
+
result = train_challenger(df, target, schema, task="regression")
|
|
254
|
+
|
|
255
|
+
payload = to_challenger_upload(result, n_samples=10)
|
|
256
|
+
|
|
257
|
+
assert payload["sdk_version"] == pkg_version("proxyml")
|
|
258
|
+
assert payload["proxyml_core_version"] == pkg_version("proxyml-core")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_to_challenger_upload_allows_version_override():
|
|
262
|
+
schema = _schema()
|
|
263
|
+
df = _df(seed=18)
|
|
264
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
265
|
+
result = train_challenger(df, target, schema, task="regression")
|
|
266
|
+
|
|
267
|
+
payload = to_challenger_upload(
|
|
268
|
+
result, n_samples=10, sdk_version="9.9.9", proxyml_core_version="8.8.8"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
assert payload["sdk_version"] == "9.9.9"
|
|
272
|
+
assert payload["proxyml_core_version"] == "8.8.8"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_to_challenger_upload_payload_is_json_serializable():
|
|
276
|
+
import json
|
|
277
|
+
|
|
278
|
+
schema = _schema()
|
|
279
|
+
df = _df(seed=19, n=300)
|
|
280
|
+
target = np.where(df["age"] > 50, "senior", "junior")
|
|
281
|
+
result = train_challenger(df, target, schema, task="classification")
|
|
282
|
+
champion_metrics = score_champion(target, target, task="classification")
|
|
283
|
+
|
|
284
|
+
payload = to_challenger_upload(result, n_samples=300, champion_metrics=champion_metrics)
|
|
285
|
+
|
|
286
|
+
json.dumps(payload) # must not raise
|
|
287
|
+
|
|
288
|
+
|
|
184
289
|
def test_train_auto_challenger_passes_immutable_cols_to_get_schema():
|
|
185
290
|
from unittest.mock import patch
|
|
186
291
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|