kimpton-evalrouter-sdk 0.1.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.
- kimpton_evalrouter_sdk-0.1.0/LICENSE +29 -0
- kimpton_evalrouter_sdk-0.1.0/PKG-INFO +78 -0
- kimpton_evalrouter_sdk-0.1.0/README.md +68 -0
- kimpton_evalrouter_sdk-0.1.0/pyproject.toml +19 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/__init__.py +92 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/__main__.py +3 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/_boundary.py +50 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/_helpers.py +101 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/_resources.py +330 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/_transport.py +421 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/cli.py +302 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/py.typed +0 -0
- kimpton_evalrouter_sdk-0.1.0/src/kimpton_evalrouter/types.py +774 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Kimpton EvalRouter SDK License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kimpton. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is granted to download, install, execute, and make internal copies of
|
|
6
|
+
this SDK and its documentation solely to access the EvalRouter service and to
|
|
7
|
+
integrate that access into applications you own or control, subject to your
|
|
8
|
+
applicable EvalRouter service agreement and authorization.
|
|
9
|
+
|
|
10
|
+
This is proprietary software, not an open-source license. Except for the limited
|
|
11
|
+
permission above or as required by applicable law, no permission is granted to
|
|
12
|
+
modify, redistribute, sublicense, sell, or use this software to provide a
|
|
13
|
+
competing service. Preserve this license and all copyright notices in permitted
|
|
14
|
+
copies. All rights not expressly granted are reserved.
|
|
15
|
+
|
|
16
|
+
This license covers only the distributed SDK, CLI, and accompanying
|
|
17
|
+
documentation. It grants no rights to EvalRouter backend or service source,
|
|
18
|
+
benchmarks, datasets, models, credentials, trademarks, or other excluded material.
|
|
19
|
+
Third-party dependencies remain subject to their own licenses.
|
|
20
|
+
|
|
21
|
+
Downloading this software does not create an account, grant service access,
|
|
22
|
+
provide credits, or waive service charges. Access remains subject to separate
|
|
23
|
+
account, workspace, authorization, and billing requirements.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
27
|
+
PURPOSE, AND NONINFRINGEMENT. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW,
|
|
28
|
+
THE COPYRIGHT HOLDERS SHALL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
29
|
+
LIABILITY ARISING FROM THE SOFTWARE OR ITS USE.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kimpton-evalrouter-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed Kimpton evaluation client and command-line interface
|
|
5
|
+
License-Expression: LicenseRef-Kimpton-EvalRouter-SDK
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: <3.14,>=3.12
|
|
8
|
+
Requires-Dist: httpx==0.28.1
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# EvalRouter Python SDK and CLI
|
|
12
|
+
|
|
13
|
+
Python SDK and CLI for Python 3.12–3.13, distributed under the proprietary
|
|
14
|
+
[Kimpton EvalRouter SDK License](LICENSE). Discover evaluations and models,
|
|
15
|
+
submit bounded evaluation runs, inspect results, and export reports.
|
|
16
|
+
|
|
17
|
+
Install with `python -m pip install kimpton-evalrouter-sdk==0.1.0`. The same
|
|
18
|
+
installation supplies `kimpton_evalrouter` and the `evalrouter` console command.
|
|
19
|
+
No repository checkout or editable installation is required.
|
|
20
|
+
|
|
21
|
+
Set `EVALROUTER_BASE_URL` to the configured API origin, `EVALROUTER_API_KEY` to a
|
|
22
|
+
workspace API key, and `EVALROUTER_WORKSPACE_ID` to its workspace. The public API
|
|
23
|
+
origin is `https://api.evalrouter.ai`. Package download does not grant service
|
|
24
|
+
access. While website access is restricted, obtain an invitation from your
|
|
25
|
+
EvalRouter operator, then create and fund the account in the web application.
|
|
26
|
+
Never paste credentials into command arguments, source files, logs, or browser
|
|
27
|
+
code. HTTPS is required outside explicit loopback development (`--allow-http`).
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from kimpton_evalrouter import Client
|
|
31
|
+
|
|
32
|
+
with Client() as client:
|
|
33
|
+
available = client.evals.list()
|
|
34
|
+
# Choose an admitted exact eval reference and compatible model from discovery.
|
|
35
|
+
run = client.run(
|
|
36
|
+
model="YOUR_MANAGED_MODEL",
|
|
37
|
+
eval="YOUR_EXACT_EVAL_REFERENCE",
|
|
38
|
+
budget_usd="1.00",
|
|
39
|
+
idempotency_key="my-durable-evaluation-request",
|
|
40
|
+
)
|
|
41
|
+
result = client.runs.results(run["id"])
|
|
42
|
+
client.runs.export_to_file(run["id"], "result.json", params={"format": "json"})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The budget is a cap, not a price guarantee or promise that a particular evaluation
|
|
46
|
+
fits. Check catalog availability and coverage. Managed routes and checked
|
|
47
|
+
`connection:<id>` endpoints have distinct billing bases. A quote does not execute
|
|
48
|
+
work. Reuse an idempotency key for retries of the same authorized submission.
|
|
49
|
+
|
|
50
|
+
For explicit quote review, create a documented `NewQuote` JSON object in
|
|
51
|
+
`quote.json`, then use the returned quote ID:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
evalrouter catalog --json
|
|
55
|
+
evalrouter catalog --models --json
|
|
56
|
+
evalrouter quote --config quote.json --json
|
|
57
|
+
evalrouter run --quote YOUR_QUOTE_ID --idempotency-key my-durable-request --wait --json
|
|
58
|
+
evalrouter results YOUR_RUN_ID --json
|
|
59
|
+
evalrouter export YOUR_RUN_ID --format json --output result.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The supported CLI commands are `catalog`, `quote`, `run`, `status`, `wait`,
|
|
63
|
+
`cancel`, `results`, `export`, and `connections list/create/check/update/disable`.
|
|
64
|
+
Run a command with `--help` for flags. Connection credentials are read through
|
|
65
|
+
`--key-env`, never a credential argument. JSON input can use `--config -` for
|
|
66
|
+
stdin. `--json` prints one result on stdout; progress is on stderr. Exit status
|
|
67
|
+
0 means success, 1 an API/transport or failed-run error, 2 invalid input or a
|
|
68
|
+
rejected request, and 4 a partial/cancelled waited run. Interrupting a local wait
|
|
69
|
+
does not cancel server work. Use `cancel` explicitly.
|
|
70
|
+
|
|
71
|
+
The Python API exposes `Client` (`EvalRouter` alias), `catalog`, `evals`,
|
|
72
|
+
`connections`, `quotes`, `evaluations`, `runs`, their request/response types, and
|
|
73
|
+
bounded wait/export helpers. `catalog.evals`/`catalog.eval` retain the discovery
|
|
74
|
+
aliases. Errors are `ClientError`, `APIError`, `RequestTimeout` and `WaitCancelled`.
|
|
75
|
+
Use `RequestOptions` for request deadlines and local cancellation. JSON/CSV/HTML
|
|
76
|
+
exports require an explicit destination and refuse overwrite unless requested.
|
|
77
|
+
Account setup and key creation are separate web steps; the CLI uses an existing
|
|
78
|
+
workspace API key.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# EvalRouter Python SDK and CLI
|
|
2
|
+
|
|
3
|
+
Python SDK and CLI for Python 3.12–3.13, distributed under the proprietary
|
|
4
|
+
[Kimpton EvalRouter SDK License](LICENSE). Discover evaluations and models,
|
|
5
|
+
submit bounded evaluation runs, inspect results, and export reports.
|
|
6
|
+
|
|
7
|
+
Install with `python -m pip install kimpton-evalrouter-sdk==0.1.0`. The same
|
|
8
|
+
installation supplies `kimpton_evalrouter` and the `evalrouter` console command.
|
|
9
|
+
No repository checkout or editable installation is required.
|
|
10
|
+
|
|
11
|
+
Set `EVALROUTER_BASE_URL` to the configured API origin, `EVALROUTER_API_KEY` to a
|
|
12
|
+
workspace API key, and `EVALROUTER_WORKSPACE_ID` to its workspace. The public API
|
|
13
|
+
origin is `https://api.evalrouter.ai`. Package download does not grant service
|
|
14
|
+
access. While website access is restricted, obtain an invitation from your
|
|
15
|
+
EvalRouter operator, then create and fund the account in the web application.
|
|
16
|
+
Never paste credentials into command arguments, source files, logs, or browser
|
|
17
|
+
code. HTTPS is required outside explicit loopback development (`--allow-http`).
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from kimpton_evalrouter import Client
|
|
21
|
+
|
|
22
|
+
with Client() as client:
|
|
23
|
+
available = client.evals.list()
|
|
24
|
+
# Choose an admitted exact eval reference and compatible model from discovery.
|
|
25
|
+
run = client.run(
|
|
26
|
+
model="YOUR_MANAGED_MODEL",
|
|
27
|
+
eval="YOUR_EXACT_EVAL_REFERENCE",
|
|
28
|
+
budget_usd="1.00",
|
|
29
|
+
idempotency_key="my-durable-evaluation-request",
|
|
30
|
+
)
|
|
31
|
+
result = client.runs.results(run["id"])
|
|
32
|
+
client.runs.export_to_file(run["id"], "result.json", params={"format": "json"})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The budget is a cap, not a price guarantee or promise that a particular evaluation
|
|
36
|
+
fits. Check catalog availability and coverage. Managed routes and checked
|
|
37
|
+
`connection:<id>` endpoints have distinct billing bases. A quote does not execute
|
|
38
|
+
work. Reuse an idempotency key for retries of the same authorized submission.
|
|
39
|
+
|
|
40
|
+
For explicit quote review, create a documented `NewQuote` JSON object in
|
|
41
|
+
`quote.json`, then use the returned quote ID:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
evalrouter catalog --json
|
|
45
|
+
evalrouter catalog --models --json
|
|
46
|
+
evalrouter quote --config quote.json --json
|
|
47
|
+
evalrouter run --quote YOUR_QUOTE_ID --idempotency-key my-durable-request --wait --json
|
|
48
|
+
evalrouter results YOUR_RUN_ID --json
|
|
49
|
+
evalrouter export YOUR_RUN_ID --format json --output result.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The supported CLI commands are `catalog`, `quote`, `run`, `status`, `wait`,
|
|
53
|
+
`cancel`, `results`, `export`, and `connections list/create/check/update/disable`.
|
|
54
|
+
Run a command with `--help` for flags. Connection credentials are read through
|
|
55
|
+
`--key-env`, never a credential argument. JSON input can use `--config -` for
|
|
56
|
+
stdin. `--json` prints one result on stdout; progress is on stderr. Exit status
|
|
57
|
+
0 means success, 1 an API/transport or failed-run error, 2 invalid input or a
|
|
58
|
+
rejected request, and 4 a partial/cancelled waited run. Interrupting a local wait
|
|
59
|
+
does not cancel server work. Use `cancel` explicitly.
|
|
60
|
+
|
|
61
|
+
The Python API exposes `Client` (`EvalRouter` alias), `catalog`, `evals`,
|
|
62
|
+
`connections`, `quotes`, `evaluations`, `runs`, their request/response types, and
|
|
63
|
+
bounded wait/export helpers. `catalog.evals`/`catalog.eval` retain the discovery
|
|
64
|
+
aliases. Errors are `ClientError`, `APIError`, `RequestTimeout` and `WaitCancelled`.
|
|
65
|
+
Use `RequestOptions` for request deadlines and local cancellation. JSON/CSV/HTML
|
|
66
|
+
exports require an explicit destination and refuse overwrite unless requested.
|
|
67
|
+
Account setup and key creation are separate web steps; the CLI uses an existing
|
|
68
|
+
workspace API key.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling==1.27.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kimpton-evalrouter-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Typed Kimpton evaluation client and command-line interface"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "LicenseRef-Kimpton-EvalRouter-SDK"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.12,<3.14"
|
|
13
|
+
dependencies = ["httpx==0.28.1"]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
evalrouter = "kimpton_evalrouter.cli:main"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["src/kimpton_evalrouter"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Typed API client. This package is independent of the API and native runners."""
|
|
2
|
+
|
|
3
|
+
from functools import cached_property as _cached_property
|
|
4
|
+
|
|
5
|
+
from . import types
|
|
6
|
+
from ._resources import Catalog as _Catalog
|
|
7
|
+
from ._resources import Connections as _Connections
|
|
8
|
+
from ._resources import Evals as _Evals
|
|
9
|
+
from ._resources import Evaluations as _Evaluations
|
|
10
|
+
from ._resources import Quotes as _Quotes
|
|
11
|
+
from ._resources import Runs as _Runs
|
|
12
|
+
from ._transport import APIError, ClientError, RequestOptions, RequestTimeout, WaitCancelled
|
|
13
|
+
from ._transport import Transport as _Transport
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Client",
|
|
17
|
+
"EvalRouter",
|
|
18
|
+
"APIError",
|
|
19
|
+
"ClientError",
|
|
20
|
+
"RequestOptions",
|
|
21
|
+
"RequestTimeout",
|
|
22
|
+
"WaitCancelled",
|
|
23
|
+
"types",
|
|
24
|
+
]
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Client(_Transport):
|
|
29
|
+
@_cached_property
|
|
30
|
+
def catalog(self) -> _Catalog:
|
|
31
|
+
return _Catalog(self)
|
|
32
|
+
|
|
33
|
+
@_cached_property
|
|
34
|
+
def connections(self) -> _Connections:
|
|
35
|
+
return _Connections(self)
|
|
36
|
+
|
|
37
|
+
@_cached_property
|
|
38
|
+
def quotes(self) -> _Quotes:
|
|
39
|
+
return _Quotes(self)
|
|
40
|
+
|
|
41
|
+
@_cached_property
|
|
42
|
+
def runs(self) -> _Runs:
|
|
43
|
+
return _Runs(self)
|
|
44
|
+
|
|
45
|
+
@_cached_property
|
|
46
|
+
def evals(self) -> _Evals:
|
|
47
|
+
return _Evals(self)
|
|
48
|
+
|
|
49
|
+
@_cached_property
|
|
50
|
+
def evaluations(self) -> _Evaluations:
|
|
51
|
+
return _Evaluations(self)
|
|
52
|
+
|
|
53
|
+
def run(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
model: str,
|
|
57
|
+
eval: str,
|
|
58
|
+
budget_usd: str | float | None = None,
|
|
59
|
+
max_charge_microusd: str | None = None,
|
|
60
|
+
provider: str | None = None,
|
|
61
|
+
coverage: dict | None = None,
|
|
62
|
+
name: str = "Evaluation",
|
|
63
|
+
metadata: dict[str, str] | None = None,
|
|
64
|
+
idempotency_key: str | None = None,
|
|
65
|
+
wait: bool = True,
|
|
66
|
+
timeout: float = 3600,
|
|
67
|
+
poll_interval: float = 2,
|
|
68
|
+
) -> "types.RunRecord":
|
|
69
|
+
"""The canonical call: route `eval` to whichever provider owns it and run it.
|
|
70
|
+
|
|
71
|
+
`model` is a managed route id or `connection:<uuid>`;
|
|
72
|
+
`eval` is `eval://provider/name/version` or `name@version`. Returns the
|
|
73
|
+
terminal run when `wait` is true, otherwise the admitted run.
|
|
74
|
+
"""
|
|
75
|
+
from uuid import uuid4
|
|
76
|
+
|
|
77
|
+
body: dict = {"model": model, "eval": eval, "name": name, "metadata": metadata or {}}
|
|
78
|
+
if provider:
|
|
79
|
+
body["provider"] = provider
|
|
80
|
+
if coverage:
|
|
81
|
+
body["coverage"] = coverage
|
|
82
|
+
if max_charge_microusd is not None:
|
|
83
|
+
body["max_charge_microusd"] = max_charge_microusd
|
|
84
|
+
else:
|
|
85
|
+
body["budget_usd"] = str(budget_usd)
|
|
86
|
+
run = self.evaluations.create(body, idempotency_key=idempotency_key or f"run-{uuid4()}")
|
|
87
|
+
if not wait:
|
|
88
|
+
return run
|
|
89
|
+
return self.runs.wait(str(run["id"]), timeout=timeout, poll_interval=poll_interval)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
EvalRouter = Client
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Validate supported evaluation inputs before issuing a request."""
|
|
2
|
+
|
|
3
|
+
from ._transport import ClientError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def validate_submission(resource, body):
|
|
7
|
+
allowed = (
|
|
8
|
+
{"selection", "model", "coverage", "max_charge_microusd"}
|
|
9
|
+
if resource == "quotes"
|
|
10
|
+
else {
|
|
11
|
+
"model",
|
|
12
|
+
"eval",
|
|
13
|
+
"budget_usd",
|
|
14
|
+
"max_charge_microusd",
|
|
15
|
+
"provider",
|
|
16
|
+
"coverage",
|
|
17
|
+
"name",
|
|
18
|
+
"metadata",
|
|
19
|
+
"split",
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
if not isinstance(body, dict) or set(body) - allowed:
|
|
23
|
+
raise ClientError(
|
|
24
|
+
"unsupported_feature", "This submission contains unsupported input fields."
|
|
25
|
+
)
|
|
26
|
+
model = body.get("model")
|
|
27
|
+
if isinstance(model, str) and resource == "evaluations":
|
|
28
|
+
valid = bool(model) and not model.startswith(("checkpoint:", "agent:", "artifact:"))
|
|
29
|
+
elif isinstance(model, dict):
|
|
30
|
+
keys = {"managed": {"kind", "route_id"}, "connection": {"kind", "connection_id"}}
|
|
31
|
+
kind = model.get("kind")
|
|
32
|
+
valid = isinstance(kind, str) and kind in keys and set(model) == keys[kind]
|
|
33
|
+
else:
|
|
34
|
+
valid = False
|
|
35
|
+
if resource == "quotes":
|
|
36
|
+
selection = body.get("selection")
|
|
37
|
+
valid = (
|
|
38
|
+
valid
|
|
39
|
+
and isinstance(selection, dict)
|
|
40
|
+
and (
|
|
41
|
+
set(selection) == {"profile_ids"}
|
|
42
|
+
or "eval" in selection
|
|
43
|
+
and not set(selection) - {"eval", "provider", "split"}
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
if not valid:
|
|
47
|
+
raise ClientError(
|
|
48
|
+
"unsupported_feature",
|
|
49
|
+
"Choose an admitted eval and a managed model or checked connection.",
|
|
50
|
+
)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Local waiting and explicit export destinations; neither starts evaluations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from . import types
|
|
13
|
+
from ._transport import ClientError, RequestOptions, Resource, checkpoint, pause, positive
|
|
14
|
+
|
|
15
|
+
TERMINAL = {"completed", "partial", "failed", "cancelled"}
|
|
16
|
+
STATES = TERMINAL | {"queued", "running", "cancelling", "finalizing"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RunHelpers(Resource):
|
|
20
|
+
def get(self, run_id: str, *, options: RequestOptions | None = None) -> types.RunRecord:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def export(
|
|
24
|
+
self,
|
|
25
|
+
run_id: str,
|
|
26
|
+
*,
|
|
27
|
+
params: types.RunsExportParams | None = None,
|
|
28
|
+
options: RequestOptions | None = None,
|
|
29
|
+
) -> bytes:
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
def wait(
|
|
33
|
+
self,
|
|
34
|
+
run_id: str,
|
|
35
|
+
*,
|
|
36
|
+
timeout: float = 3600,
|
|
37
|
+
poll_interval: float = 2,
|
|
38
|
+
cancel_event: threading.Event | None = None,
|
|
39
|
+
on_progress: Callable[[types.RunRecord], None] | None = None,
|
|
40
|
+
) -> types.RunRecord:
|
|
41
|
+
deadline = time.monotonic() + positive(timeout, "Wait timeout")
|
|
42
|
+
interval = positive(poll_interval, "Poll interval")
|
|
43
|
+
while True:
|
|
44
|
+
remaining = checkpoint(deadline, cancel_event)
|
|
45
|
+
run = self.get(
|
|
46
|
+
run_id, options=RequestOptions(timeout=remaining, cancel_event=cancel_event)
|
|
47
|
+
)
|
|
48
|
+
checkpoint(deadline, cancel_event)
|
|
49
|
+
if run.get("status") not in STATES:
|
|
50
|
+
raise ClientError("invalid_response", "API returned an unknown evaluation state.")
|
|
51
|
+
if on_progress:
|
|
52
|
+
on_progress(run)
|
|
53
|
+
checkpoint(deadline, cancel_event)
|
|
54
|
+
if run["status"] in TERMINAL:
|
|
55
|
+
return run
|
|
56
|
+
pause(interval, deadline, cancel_event)
|
|
57
|
+
|
|
58
|
+
def export_to_file(
|
|
59
|
+
self,
|
|
60
|
+
run_id: str,
|
|
61
|
+
destination: str | Path,
|
|
62
|
+
*,
|
|
63
|
+
params: types.RunsExportParams | None = None,
|
|
64
|
+
options: RequestOptions | None = None,
|
|
65
|
+
overwrite: bool = False,
|
|
66
|
+
) -> Path:
|
|
67
|
+
path = Path(destination)
|
|
68
|
+
if not overwrite and path.exists():
|
|
69
|
+
raise ClientError("destination_exists", "Export destination already exists.")
|
|
70
|
+
data = self.export(run_id, params=params, options=options)
|
|
71
|
+
return save_export(data, path, overwrite=overwrite)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def save_export(data: bytes, destination: str | Path, *, overwrite: bool = False) -> Path:
|
|
75
|
+
path = Path(destination)
|
|
76
|
+
if not overwrite and path.exists():
|
|
77
|
+
raise ClientError("destination_exists", "Export destination already exists.")
|
|
78
|
+
temporary = None
|
|
79
|
+
try:
|
|
80
|
+
with tempfile.NamedTemporaryFile(
|
|
81
|
+
dir=path.parent, prefix=".kimpton-export-", delete=False
|
|
82
|
+
) as stream:
|
|
83
|
+
temporary = Path(stream.name)
|
|
84
|
+
stream.write(data)
|
|
85
|
+
stream.flush()
|
|
86
|
+
os.fsync(stream.fileno())
|
|
87
|
+
if overwrite:
|
|
88
|
+
os.replace(temporary, path)
|
|
89
|
+
else:
|
|
90
|
+
os.link(temporary, path)
|
|
91
|
+
temporary.unlink()
|
|
92
|
+
return path
|
|
93
|
+
except FileExistsError:
|
|
94
|
+
raise ClientError("destination_exists", "Export destination already exists.") from None
|
|
95
|
+
except OSError:
|
|
96
|
+
raise ClientError(
|
|
97
|
+
"export_write_failed", "Could not write the export destination."
|
|
98
|
+
) from None
|
|
99
|
+
finally:
|
|
100
|
+
if temporary is not None:
|
|
101
|
+
temporary.unlink(missing_ok=True)
|