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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.4.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.4.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.4.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"
@@ -12,4 +12,4 @@ black
12
12
  ruff
13
13
 
14
14
  [local]
15
- proxyml-core[modeling]>=0.2
15
+ proxyml-core[modeling]>=0.2.1
@@ -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