skilltest-sdk 0.1.1__tar.gz → 0.2.2__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.
@@ -15,6 +15,11 @@ dist/
15
15
  node_modules/
16
16
  *.tsbuildinfo
17
17
 
18
+ # Bundled CLI binaries injected at release time into the per-platform npm
19
+ # packages and the Python wheel; never committed — built/downloaded at publish.
20
+ /sdks/typescript/platforms/*/bin/
21
+ /sdks/python/skilltest_sdk/_bin/
22
+
18
23
  # Nx
19
24
  .nx/cache
20
25
  .nx/workspace-data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skilltest-sdk
3
- Version: 0.1.1
3
+ Version: 0.2.2
4
4
  Summary: Python SDK for the skilltest CLI: run AI-skill tests and natural-language evals from Python, with a typed report contract.
5
5
  Author: Nick DeRobertis
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "skilltest-sdk"
3
- version = "0.1.1"
3
+ version = "0.2.2"
4
4
  description = "Python SDK for the skilltest CLI: run AI-skill tests and natural-language evals from Python, with a typed report contract."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -25,6 +25,12 @@ build-backend = "hatchling.build"
25
25
  [tool.hatch.build.targets.wheel]
26
26
  packages = ["skilltest_sdk"]
27
27
 
28
+ # A platform build drops the prebuilt CLI at skilltest_sdk/_bin/skilltest (which
29
+ # is git-ignored), then `scripts/build-python-wheel.sh` retags the wheel for the
30
+ # target platform. Listing it as an artifact overrides the VCS-ignore so the
31
+ # binary is packed; the pure (py3-none-any) build simply has nothing to match.
32
+ artifacts = ["skilltest_sdk/_bin/*"]
33
+
28
34
  [tool.ruff]
29
35
  line-length = 100
30
36
  target-version = "py312"
@@ -7,6 +7,7 @@ deterministic checks against the transcript.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import contextlib
10
11
  import os
11
12
  import subprocess
12
13
  from collections.abc import Sequence
@@ -26,10 +27,37 @@ ENV_PROVIDER = "SKILLTEST_PROVIDER"
26
27
  _REPORTING_CODES = frozenset({0, 1})
27
28
 
28
29
 
30
+ #: Name of the bundled binary inside the wheel's ``_bin/`` directory.
31
+ _BIN_NAME = "skilltest.exe" if os.name == "nt" else "skilltest"
32
+
33
+
34
+ def _bundled_bin() -> str | None:
35
+ """Path to the binary bundled in this wheel, or ``None`` when absent.
36
+
37
+ Platform wheels ship the prebuilt CLI at ``skilltest_sdk/_bin/skilltest``;
38
+ the pure (``py3-none-any``) wheel and a source checkout ship none, so callers
39
+ fall back to ``$SKILLTEST_BIN``/``PATH``. Wheel packing can drop the
40
+ executable bit, so restore it best-effort before handing back the path.
41
+ """
42
+ candidate = Path(__file__).resolve().parent / "_bin" / _BIN_NAME
43
+ if not candidate.is_file():
44
+ return None
45
+ if not os.access(candidate, os.X_OK):
46
+ with contextlib.suppress(OSError):
47
+ candidate.chmod(0o755)
48
+ return str(candidate)
49
+
50
+
29
51
  def _resolve_bin(bin: str | Path | None) -> str:
52
+ """Resolve the binary, most explicit first: an explicit ``bin``, then
53
+ ``$SKILLTEST_BIN``, then the binary bundled in a platform wheel, then
54
+ ``skilltest`` on ``PATH``."""
30
55
  if bin is not None:
31
56
  return str(bin)
32
- return os.environ.get(ENV_BIN, "skilltest")
57
+ env = os.environ.get(ENV_BIN)
58
+ if env:
59
+ return env
60
+ return _bundled_bin() or "skilltest"
33
61
 
34
62
 
35
63
  def _resolve_provider(provider: str | Sequence[str] | None) -> str | None:
@@ -0,0 +1,48 @@
1
+ """Unit tests for binary resolution: the precedence chain (explicit > env >
2
+ bundled wheel binary > PATH) and that a binary bundled in the wheel's ``_bin/``
3
+ is discovered when present.
4
+
5
+ A source checkout and the pure (``py3-none-any``) wheel ship no binary, so
6
+ ``_bundled_bin()`` is ``None`` and the runner falls back — exactly how the e2e
7
+ suite reaches the locally built CLI via ``$SKILLTEST_BIN``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ import pytest
15
+
16
+ from skilltest_sdk import runner
17
+
18
+
19
+ def test_explicit_bin_wins(monkeypatch: pytest.MonkeyPatch) -> None:
20
+ monkeypatch.setenv(runner.ENV_BIN, "/from/env")
21
+ assert runner._resolve_bin("/explicit") == "/explicit"
22
+
23
+
24
+ def test_env_beats_bundled_and_path(monkeypatch: pytest.MonkeyPatch) -> None:
25
+ monkeypatch.setenv(runner.ENV_BIN, "/from/env")
26
+ assert runner._resolve_bin(None) == "/from/env"
27
+
28
+
29
+ def test_falls_back_to_path_when_unset_and_unbundled(monkeypatch: pytest.MonkeyPatch) -> None:
30
+ monkeypatch.delenv(runner.ENV_BIN, raising=False)
31
+ assert runner._bundled_bin() is None
32
+ assert runner._resolve_bin(None) == "skilltest"
33
+
34
+
35
+ def test_bundled_binary_is_found_and_preferred(monkeypatch: pytest.MonkeyPatch) -> None:
36
+ monkeypatch.delenv(runner.ENV_BIN, raising=False)
37
+ bin_dir = Path(runner.__file__).resolve().parent / "_bin"
38
+ bin_path = bin_dir / runner._BIN_NAME
39
+ bin_dir.mkdir(parents=True, exist_ok=True)
40
+ try:
41
+ bin_path.write_text("#!/bin/sh\n")
42
+ bin_path.chmod(0o755)
43
+ assert runner._bundled_bin() == str(bin_path)
44
+ assert runner._resolve_bin(None) == str(bin_path)
45
+ finally:
46
+ bin_path.unlink(missing_ok=True)
47
+ if bin_dir.is_dir() and not any(bin_dir.iterdir()):
48
+ bin_dir.rmdir()
@@ -482,7 +482,7 @@ wheels = [
482
482
 
483
483
  [[package]]
484
484
  name = "skilltest-sdk"
485
- version = "0.1.1"
485
+ version = "0.2.2"
486
486
  source = { editable = "." }
487
487
  dependencies = [
488
488
  { name = "pydantic" },
File without changes