proxyml 0.2.0__tar.gz → 0.2.1__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.2.0 → proxyml-0.2.1}/PKG-INFO +1 -1
- {proxyml-0.2.0 → proxyml-0.2.1}/pyproject.toml +1 -1
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml/__init__.py +2 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml/client.py +89 -42
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml/schema.py +7 -2
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml.egg-info/PKG-INFO +1 -1
- {proxyml-0.2.0 → proxyml-0.2.1}/tests/test_client.py +28 -1
- {proxyml-0.2.0 → proxyml-0.2.1}/LICENSE +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/README.md +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/setup.cfg +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml.egg-info/requires.txt +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.2.0 → proxyml-0.2.1}/tests/test_schema.py +0 -0
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
2
|
+
import os
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
import orjson
|
|
6
|
-
import requests
|
|
7
|
-
import os
|
|
8
6
|
import numpy as np
|
|
9
7
|
import pandas as pd
|
|
8
|
+
import requests
|
|
10
9
|
from pandas.api.types import is_float_dtype, is_integer_dtype
|
|
11
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
13
|
_BOOL_STRINGS = {"true", "false"}
|
|
14
|
+
_TIMEOUT = 120
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _ErrorResponse:
|
|
18
|
+
"""Returned by HTTP helpers when a network-level error occurs."""
|
|
19
|
+
status_code = 0
|
|
20
|
+
text = ""
|
|
21
|
+
|
|
22
|
+
def json(self) -> dict:
|
|
23
|
+
return {}
|
|
14
24
|
|
|
15
25
|
|
|
16
26
|
def _base_url() -> str:
|
|
@@ -33,7 +43,7 @@ def _headers() -> dict:
|
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
|
|
36
|
-
def post(endpoint: str, payload: dict) -> requests.models.Response:
|
|
46
|
+
def post(endpoint: str, payload: dict) -> requests.models.Response | _ErrorResponse:
|
|
37
47
|
"""
|
|
38
48
|
POSTs a request to a ProxyML API endpoint.
|
|
39
49
|
|
|
@@ -44,53 +54,65 @@ def post(endpoint: str, payload: dict) -> requests.models.Response:
|
|
|
44
54
|
Returns:
|
|
45
55
|
requests Response object.
|
|
46
56
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
try:
|
|
58
|
+
return requests.post(
|
|
59
|
+
url=f'{_base_url()}{endpoint}',
|
|
60
|
+
data=orjson.dumps(payload, option=orjson.OPT_SERIALIZE_NUMPY),
|
|
61
|
+
headers=_headers(),
|
|
62
|
+
timeout=_TIMEOUT,
|
|
63
|
+
)
|
|
64
|
+
except requests.exceptions.RequestException as exc:
|
|
65
|
+
logger.error("Network error POSTing to %s: %s", endpoint, exc)
|
|
66
|
+
return _ErrorResponse()
|
|
53
67
|
|
|
54
68
|
|
|
55
|
-
def put(endpoint: str, payload: dict) -> requests.models.Response:
|
|
69
|
+
def put(endpoint: str, payload: dict) -> requests.models.Response | _ErrorResponse:
|
|
56
70
|
"""
|
|
57
71
|
PUTs a request to a ProxyML API endpoint.
|
|
58
72
|
|
|
59
73
|
Args:
|
|
60
74
|
endpoint (str): ProxyML API endpoint. PROXYML_BASE_URL is prepended e.g. use endpoint='/schemas' not 'https://api.proxyml.ai/api/v1/schemas'.
|
|
61
|
-
payload (dict): JSON payload to
|
|
75
|
+
payload (dict): JSON payload to PUT.
|
|
62
76
|
|
|
63
77
|
Returns:
|
|
64
78
|
requests Response object.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
return requests.put(
|
|
82
|
+
url=f'{_base_url()}{endpoint}',
|
|
83
|
+
data=orjson.dumps(payload, option=orjson.OPT_SERIALIZE_NUMPY),
|
|
84
|
+
headers=_headers(),
|
|
85
|
+
timeout=_TIMEOUT,
|
|
86
|
+
)
|
|
87
|
+
except requests.exceptions.RequestException as exc:
|
|
88
|
+
logger.error("Network error PUTting to %s: %s", endpoint, exc)
|
|
89
|
+
return _ErrorResponse()
|
|
72
90
|
|
|
73
91
|
|
|
74
|
-
def get(endpoint: str, params: dict) -> requests.models.Response:
|
|
92
|
+
def get(endpoint: str, params: dict | None = None) -> requests.models.Response | _ErrorResponse:
|
|
75
93
|
"""
|
|
76
94
|
GETs a request to a ProxyML API endpoint.
|
|
77
95
|
|
|
78
96
|
Args:
|
|
79
97
|
endpoint (str): ProxyML API endpoint. PROXYML_BASE_URL is prepended e.g. use endpoint='/schemas' not 'https://api.proxyml.ai/api/v1/schemas'.
|
|
80
|
-
params (dict):
|
|
98
|
+
params (dict): query parameters.
|
|
81
99
|
|
|
82
100
|
Returns:
|
|
83
101
|
requests Response object.
|
|
84
102
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
try:
|
|
104
|
+
return requests.get(
|
|
105
|
+
url=f'{_base_url()}{endpoint}',
|
|
106
|
+
headers=_headers(),
|
|
107
|
+
params=params,
|
|
108
|
+
timeout=_TIMEOUT,
|
|
109
|
+
)
|
|
110
|
+
except requests.exceptions.RequestException as exc:
|
|
111
|
+
logger.error("Network error GETting %s: %s", endpoint, exc)
|
|
112
|
+
return _ErrorResponse()
|
|
91
113
|
|
|
92
114
|
|
|
93
|
-
def patch(endpoint: str, payload: dict) -> requests.models.Response:
|
|
115
|
+
def patch(endpoint: str, payload: dict) -> requests.models.Response | _ErrorResponse:
|
|
94
116
|
"""
|
|
95
117
|
PATCHes a request to a ProxyML API endpoint.
|
|
96
118
|
|
|
@@ -101,15 +123,19 @@ def patch(endpoint: str, payload: dict) -> requests.models.Response:
|
|
|
101
123
|
Returns:
|
|
102
124
|
requests Response object.
|
|
103
125
|
"""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
try:
|
|
127
|
+
return requests.patch(
|
|
128
|
+
url=f'{_base_url()}{endpoint}',
|
|
129
|
+
data=orjson.dumps(payload, option=orjson.OPT_SERIALIZE_NUMPY),
|
|
130
|
+
headers=_headers(),
|
|
131
|
+
timeout=_TIMEOUT,
|
|
132
|
+
)
|
|
133
|
+
except requests.exceptions.RequestException as exc:
|
|
134
|
+
logger.error("Network error PATCHing %s: %s", endpoint, exc)
|
|
135
|
+
return _ErrorResponse()
|
|
110
136
|
|
|
111
137
|
|
|
112
|
-
def delete(endpoint: str) -> requests.models.Response:
|
|
138
|
+
def delete(endpoint: str) -> requests.models.Response | _ErrorResponse:
|
|
113
139
|
"""
|
|
114
140
|
DELETE request to a ProxyML API endpoint.
|
|
115
141
|
|
|
@@ -119,11 +145,15 @@ def delete(endpoint: str) -> requests.models.Response:
|
|
|
119
145
|
Returns:
|
|
120
146
|
requests Response object.
|
|
121
147
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
try:
|
|
149
|
+
return requests.delete(
|
|
150
|
+
url=f'{_base_url()}{endpoint}',
|
|
151
|
+
headers=_headers(),
|
|
152
|
+
timeout=_TIMEOUT,
|
|
153
|
+
)
|
|
154
|
+
except requests.exceptions.RequestException as exc:
|
|
155
|
+
logger.error("Network error DELETEing %s: %s", endpoint, exc)
|
|
156
|
+
return _ErrorResponse()
|
|
127
157
|
|
|
128
158
|
|
|
129
159
|
def put_schema(schema: dict, name: str = "default") -> Any:
|
|
@@ -345,7 +375,7 @@ def find_counterfactual(sample, target, n_neighbors: int = 10000, perturbation_s
|
|
|
345
375
|
sample (list): point around which neighbors will be found.
|
|
346
376
|
target (Any): the desired prediction outcome, can be a string or number for classification or a target predicted value for regression.
|
|
347
377
|
n_neighbors (int): the number of neighbors to search, defaults to 10000.
|
|
348
|
-
|
|
378
|
+
perturbation_scale (float): controls the "wiggle" in the features around the provided sample. Defaults to 0.1.
|
|
349
379
|
version (str): name of the surrogate model to run, defaults to "default."
|
|
350
380
|
as_df (bool): if True, return the counterfactual as a dataframe. If False, returns JSON object.
|
|
351
381
|
Returns:
|
|
@@ -467,7 +497,7 @@ def find_counterfactuals(
|
|
|
467
497
|
samples (list): list of points around which neighbors will be found.
|
|
468
498
|
target (Any): the desired prediction outcome, can be a string or number for classification or a target predicted value for regression.
|
|
469
499
|
n_neighbors (int): the number of neighbors to search, defaults to 10000.
|
|
470
|
-
|
|
500
|
+
perturbation_scale (float): controls the "wiggle" in the features around the provided sample. Defaults to 0.1.
|
|
471
501
|
version (str): name of the surrogate model to run, defaults to "default."
|
|
472
502
|
as_df (bool): if True, return the counterfactual as a dataframe. If False, returns JSON object.
|
|
473
503
|
Returns:
|
|
@@ -592,6 +622,23 @@ def get_feature_importances(version: str | None = None) -> Any:
|
|
|
592
622
|
return None
|
|
593
623
|
|
|
594
624
|
|
|
625
|
+
def health_check() -> dict | None:
|
|
626
|
+
"""Check API connectivity and version. Does not require authentication and does not count against usage quota.
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
Dict with ``status``, ``model_loaded``, and ``version``, or None on failure.
|
|
630
|
+
"""
|
|
631
|
+
try:
|
|
632
|
+
r = requests.get(url=f'{_base_url()}/health', timeout=_TIMEOUT)
|
|
633
|
+
except requests.exceptions.RequestException as exc:
|
|
634
|
+
logger.error("Network error calling /health: %s", exc)
|
|
635
|
+
return None
|
|
636
|
+
if r.status_code == 200:
|
|
637
|
+
return r.json()
|
|
638
|
+
logger.error("Health check failed with status %s: %s", r.status_code, r.text)
|
|
639
|
+
return None
|
|
640
|
+
|
|
641
|
+
|
|
595
642
|
def get_usage() -> dict | None:
|
|
596
643
|
"""Return current tier, usage counts, and quota for the authenticated user."""
|
|
597
644
|
r = get(endpoint='/account/usage', params={})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
logger = logging.getLogger(__name__)
|
|
3
2
|
|
|
4
|
-
import os
|
|
5
3
|
import numpy as np
|
|
6
4
|
import pandas as pd
|
|
7
5
|
from pandas.api.types import is_float_dtype, is_integer_dtype
|
|
8
6
|
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
9
|
|
|
10
10
|
def gen_continuous_schema(s: pd.Series, name: str | None = None) -> dict:
|
|
11
11
|
"""
|
|
@@ -73,6 +73,11 @@ def get_schema(df: pd.DataFrame, immutable_cols: list[str] | None) -> dict:
|
|
|
73
73
|
Returns:
|
|
74
74
|
data schema as dict. Review and adjust as needed.
|
|
75
75
|
"""
|
|
76
|
+
if immutable_cols:
|
|
77
|
+
unknown = set(immutable_cols) - set(df.columns)
|
|
78
|
+
if unknown:
|
|
79
|
+
logger.warning("immutable_cols not found in DataFrame and will be ignored: %s", sorted(unknown))
|
|
80
|
+
|
|
76
81
|
schema = {
|
|
77
82
|
'features': list(),
|
|
78
83
|
'_note': (
|
|
@@ -3,11 +3,13 @@ from unittest.mock import MagicMock, patch
|
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import pytest
|
|
6
|
+
import requests
|
|
6
7
|
|
|
7
8
|
from proxyml.client import (
|
|
8
9
|
_cast_column,
|
|
9
10
|
_base_url,
|
|
10
11
|
_headers,
|
|
12
|
+
health_check,
|
|
11
13
|
delete_model,
|
|
12
14
|
delete_schema,
|
|
13
15
|
diff_models,
|
|
@@ -34,6 +36,31 @@ from proxyml.client import (
|
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# health_check
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
@patch("proxyml.client.requests.get")
|
|
44
|
+
def test_health_check_success(mock_get):
|
|
45
|
+
mock_get.return_value = _mock_response(200, {"status": "ok", "model_loaded": True, "version": "0.1.0"})
|
|
46
|
+
result = health_check()
|
|
47
|
+
assert result == {"status": "ok", "model_loaded": True, "version": "0.1.0"}
|
|
48
|
+
mock_get.assert_called_once()
|
|
49
|
+
assert mock_get.call_args.kwargs["url"].endswith("/health")
|
|
50
|
+
assert "headers" not in mock_get.call_args.kwargs # no auth header
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@patch("proxyml.client.requests.get")
|
|
54
|
+
def test_health_check_failure_returns_none(mock_get):
|
|
55
|
+
mock_get.return_value = _mock_response(503, {"detail": "unavailable"})
|
|
56
|
+
assert health_check() is None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@patch("proxyml.client.requests.get", side_effect=requests.exceptions.ConnectionError("connection refused"))
|
|
60
|
+
def test_health_check_network_error_returns_none(mock_get):
|
|
61
|
+
assert health_check() is None
|
|
62
|
+
|
|
63
|
+
|
|
37
64
|
# ---------------------------------------------------------------------------
|
|
38
65
|
# _headers
|
|
39
66
|
# ---------------------------------------------------------------------------
|
|
@@ -312,7 +339,7 @@ def test_train_surrogate_with_metadata(mock_post):
|
|
|
312
339
|
mock_post.return_value = _mock_response(200, {
|
|
313
340
|
"version": "abc-123", "trained_at": "2026-04-19T12:00:00",
|
|
314
341
|
"task": "regression", "name": "v1", "comments": "test run",
|
|
315
|
-
"feature_names": None, "metrics": {"r2": 0.95}, "
|
|
342
|
+
"feature_names": None, "metrics": {"r2": 0.95}, "warnings": [],
|
|
316
343
|
})
|
|
317
344
|
result = train_surrogate(
|
|
318
345
|
samples=[[1.0, 2.0]], predictions=[3.0],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|