diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a9c2cb..a95c156 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## [v0.2.0] - 2026-07-02
+
+### Added
+
+- Added a `request_timeout_seconds` constructor parameter to `PipelexAPIClient`, setting a per-instance blocking-execute ceiling for the inherited protocol routes (`execute`, `start`, `validate`, `models`, `version`).
+
+### Changed
+
+- **BREAKING:** Renamed `PipelexAPIClient` constructor parameters and attributes to match the `mthds` base client and the `@pipelex/sdk` JavaScript counterpart: `api_token` → `api_key` and `api_base_url` → `base_url`. *(Migration: update all instantiations and property reads to the new names.)*
+- **BREAKING:** Renamed API URL environment variables for workspace-wide consistency: `PIPELEX_API_URL` → `PIPELEX_BASE_URL` and the `mthds` fallback `MTHDS_API_URL` → `MTHDS_BASE_URL`. No read aliases are kept for the old names.
+- Bumped the `mthds` dependency from `>=0.6.1` to `>=0.7.0`, and updated config loading to import `load_config` from `mthds.config` (previously `load_credentials` from `mthds.config.credentials`).
+- Updated documentation (`README.md`, `CLAUDE.md`, `docs/architecture.md`) and unit tests to reflect the new client signature, environment variables, and `mthds` configuration.
+
## [v0.1.1] - 2026-07-01
### Fixed
diff --git a/CLAUDE.md b/CLAUDE.md
index 4345a6e..2866c79 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -13,7 +13,7 @@ It is the **hosted superset**: the five normative MTHDS Protocol routes (inherit
- **One-way dependency: `pipelex-sdk → mthds`.** This package depends on `mthds` and never the reverse.
- **Inheritance, not re-implementation.** `class PipelexAPIClient(MthdsAPIClient)`. Reuse the base transport (`_send`, `_url`), body-builders, the reusable protocol methods, `runner_type`, and the async context-manager. Add lifecycle/product/health on top. The base's single-underscore transport methods are treated as a documented **protected extension surface** — do not rename or fork them.
- **Brand boundary (MTHDS vs Pipelex).** MTHDS = the open standard's brand; Pipelex = the runtime/product brand. Protocol routes and their models belong to `mthds` and keep neutral names; Pipelex-specific surfaces (lifecycle, product routes, implementation envelopes) live here. Name by which brand owns the concept.
-- **Credentials.** Resolve `PIPELEX_API_KEY` / `PIPELEX_API_URL` first, then fall back to the `mthds` resolver (`MTHDS_API_KEY` / `MTHDS_API_URL`, `~/.mthds/config`). Token is **optional** (anonymous allowed). Default base URL `https://api.pipelex.com`.
+- **Credentials.** Resolve `PIPELEX_API_KEY` / `PIPELEX_BASE_URL` first, then fall back to the `mthds` resolver (`MTHDS_API_KEY` / `MTHDS_BASE_URL`, `~/.mthds/config`). Token is **optional** (anonymous allowed). Default base URL `https://api.pipelex.com`.
- **Async-only.** httpx `AsyncClient`, `async def` throughout. No sync facade in v0.1.
- **No barrel.** `__init__.py` files stay empty — no re-exports, no docstrings. Import via full paths (`from pipelex_sdk.client import PipelexAPIClient`).
diff --git a/README.md b/README.md
index fcae8b2..3c3411c 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ pip install pipelex-sdk
## Configuration
-Credentials resolve, in order: explicit constructor arguments → `PIPELEX_API_KEY` / `PIPELEX_API_URL` → `MTHDS_API_KEY` / `MTHDS_API_URL` (and `~/.mthds/config`) → defaults. The token is **optional** — anonymous access works against the protocol routes (e.g. a local bare runner); the product routes return `401`. The default base URL is `https://api.pipelex.com`. The base URL is host-only (no path/query/fragment); every endpoint composes as `{base}/v1/{endpoint}`.
+Credentials resolve, in order: explicit constructor arguments → `PIPELEX_API_KEY` / `PIPELEX_BASE_URL` → `MTHDS_API_KEY` / `MTHDS_BASE_URL` (and `~/.mthds/config`) → defaults. The token is **optional** — anonymous access works against the protocol routes (e.g. a local bare runner); the product routes return `401`. The default base URL is `https://api.pipelex.com`. The base URL is host-only (no path/query/fragment); every endpoint composes as `{base}/v1/{endpoint}`.
The client is **async-only** (httpx `AsyncClient`) and is an async context manager.
diff --git a/docs/architecture.md b/docs/architecture.md
index 52c94cb..65a71ce 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -35,8 +35,8 @@ The Pipelex narrowing of the `/v1/validate` verdict union is one such implementa
Resolved at construction time:
-- `PIPELEX_API_KEY` / `PIPELEX_API_URL` first (brand + JS parity);
-- falling back to the `mthds` resolver (`MTHDS_API_KEY` / `MTHDS_API_URL`, `~/.mthds/config`) as a secondary source.
+- `PIPELEX_API_KEY` / `PIPELEX_BASE_URL` first (brand + JS parity);
+- falling back to the `mthds` resolver (`MTHDS_API_KEY` / `MTHDS_BASE_URL`, `~/.mthds/config`) as a secondary source.
A token is **optional** (anonymous access is allowed; protocol routes work against anonymous bare runners, product routes return `401`). The default base URL is `https://api.pipelex.com`. The base URL is validated host-only (no path/query/fragment/embedded credentials; http/https only).
diff --git a/pipelex_sdk/client.py b/pipelex_sdk/client.py
index 46d51e2..963d802 100644
--- a/pipelex_sdk/client.py
+++ b/pipelex_sdk/client.py
@@ -26,7 +26,7 @@
from urllib.parse import quote, urlparse
import httpx
-from mthds.config.credentials import load_credentials
+from mthds.config import load_config
from mthds.protocol.exceptions import PipelineRequestError
from mthds.runners.api.client import MthdsAPIClient
from pydantic import BaseModel, TypeAdapter, ValidationError
@@ -89,7 +89,7 @@
from pipelex_sdk.runs import RunResultState
from pipelex_sdk.validation_models import PipelexValidationResult
-# The client composes every endpoint from one origin (PIPELEX_API_URL): `{base}/v1/{endpoint}`.
+# The client composes every endpoint from one origin (PIPELEX_BASE_URL): `{base}/v1/{endpoint}`.
# The same paths are served by the Pipelex Hosted API (api.pipelex.com) and by a bare
# OSS pipelex-api runner (localhost:8081) — the protocol surface is identical; only the
# hosted extensions (e.g. run polling) differ, detectable via GET /v1/version.
@@ -109,7 +109,7 @@
_GATEWAY_TIMEOUT_THRESHOLD_SECONDS = 28.0
_PIPELEX_API_KEY_ENV = "PIPELEX_API_KEY"
-_PIPELEX_API_URL_ENV = "PIPELEX_API_URL"
+_PIPELEX_BASE_URL_ENV = "PIPELEX_BASE_URL"
# `VersionInfo.implementation` of the bare open-source runner (no run store). Anything
# else — the hosted implementation first — is assumed to serve the durable run-lifecycle
@@ -139,7 +139,7 @@ class MthdsFile(BaseModel):
class PipelexAPIClient(MthdsAPIClient):
"""Client for the Pipelex hosted API — and any MTHDS-compliant runner.
- One base URL (`PIPELEX_API_URL`); every endpoint is `/v1/`:
+ One base URL (`PIPELEX_BASE_URL`); every endpoint is `/v1/`:
- **protocol** (`execute` / `start` / `validate` / `models` / `version`) — inherited
from `MthdsAPIClient`; works against any MTHDS-compliant runner, hosted or bare.
- **run lifecycle** (`get_run_status` / `get_run_result` / `wait_for_result`) — the
@@ -149,30 +149,36 @@ class PipelexAPIClient(MthdsAPIClient):
on the structured `ApiResponseError.code`, not the HTTP status.
Construction resolves credentials Pipelex-first (`PIPELEX_API_KEY` /
- `PIPELEX_API_URL`), falling back to the `mthds` resolver (`MTHDS_API_KEY` /
- `MTHDS_API_URL`, `~/.mthds/config`). The token is optional — anonymous access works
+ `PIPELEX_BASE_URL`), falling back to the `mthds` resolver (`MTHDS_API_KEY` /
+ `MTHDS_BASE_URL`, `~/.mthds/config`). The token is optional — anonymous access works
against the protocol routes; product routes return `401`. The base URL is validated
host-only (no path/query/fragment/credentials; http/https only).
"""
- def __init__(self, api_token: str | None = None, api_base_url: str | None = None) -> None:
- credentials = load_credentials()
+ def __init__(self, api_key: str | None = None, base_url: str | None = None) -> None:
+ config = load_config()
- # Pipelex-primary, mthds fallback. `credentials` already layers env (MTHDS_*) >
+ # Pipelex-primary, mthds fallback. `config` already layers env (MTHDS_*) >
# file (~/.mthds/config) > default, so this ladder gives the full precedence:
- # explicit arg > PIPELEX_* env > MTHDS_* env > file > default. The token is optional
+ # explicit arg > PIPELEX_* env > MTHDS_* env > file > default. The key is optional
# and an empty string ("") means anonymous — so the first layer that is *present*
- # wins even when it is empty. We test `is not None` (not truthiness) to honor an
- # explicit `api_token=""` / `PIPELEX_API_KEY=""`, matching the JS SDK's `??` chain.
- self.api_token: str
- if api_token is not None:
- self.api_token = api_token
- elif (pipelex_env_token := os.environ.get(_PIPELEX_API_KEY_ENV)) is not None:
- self.api_token = pipelex_env_token
+ # wins even when it is empty.
+ #
+ # DO NOT collapse this into `api_key or os.environ.get(...) or config["api_key"]`.
+ # `or` treats "" as falsy and would fall through, silently discarding an explicit
+ # anonymous request (`api_key=""` / `PIPELEX_API_KEY=""`) and reaching for the next
+ # configured key instead. We test `is not None` (presence, not truthiness) precisely
+ # to honor the empty string, matching the JS SDK's `??` chain. (The `base_url` line
+ # below correctly uses `or`: there an empty value has no special meaning.)
+ self.api_key: str
+ if api_key is not None:
+ self.api_key = api_key
+ elif (pipelex_env_key := os.environ.get(_PIPELEX_API_KEY_ENV)) is not None:
+ self.api_key = pipelex_env_key
else:
- self.api_token = credentials["api_key"]
+ self.api_key = config["api_key"]
- resolved_base_url = api_base_url or os.environ.get(_PIPELEX_API_URL_ENV) or credentials["api_url"] or DEFAULT_API_BASE_URL
+ resolved_base_url = base_url or os.environ.get(_PIPELEX_BASE_URL_ENV) or config["base_url"] or DEFAULT_API_BASE_URL
normalized_base_url = resolved_base_url.rstrip("/")
# The base URL must be host-only: a path-prefixed value (e.g. `.../v1`) would
# compose as `/v1/v1/...` and fail with a misleading endpoint error instead of a
@@ -185,19 +191,23 @@ def __init__(self, api_token: str | None = None, api_base_url: str | None = None
"Endpoints compose as {base}/v1/{endpoint}."
)
raise PipelineRequestError(msg)
- self.api_base_url: str = normalized_base_url
+ self.base_url: str = normalized_base_url
#: Origin root derived from the base URL — `/health` lives here, not under `/v1`.
self.origin_url: str = _origin_of(normalized_base_url)
+ #: Per-request timeout the inherited protocol routes (`execute` / `start` / `validate`
+ #: / `models` / `version`) read — the blocking-execute ceiling. The SDK's own poll and
+ #: product GETs pass `_POLL_REQUEST_TIMEOUT_SECONDS` explicitly instead.
+ self.request_timeout_seconds: float = _DEFAULT_REQUEST_TIMEOUT_SECONDS
self.client: httpx.AsyncClient | None = None
#: Cached `/v1/version` handshake outcome — whether the durable lifecycle is served.
self._lifecycle_available: bool | None = None
@override
def start_client(self) -> PipelexAPIClient:
- """Initialize the HTTP client. The Authorization header is sent only when a token
- is configured — anonymous access (empty token) omits it, matching the JS SDK.
+ """Initialize the HTTP client. The Authorization header is sent only when a key
+ is configured — anonymous access (empty key) omits it, matching the JS SDK.
"""
- headers = {"Authorization": f"Bearer {self.api_token}"} if self.api_token else {}
+ headers = {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}
self.client = httpx.AsyncClient(headers=headers)
return self
@@ -211,12 +221,12 @@ async def _send_or_unreachable(self, method: str, url: str, *, content: bytes |
try:
return await self._send(method, url, content=content, request_timeout=request_timeout)
except httpx.TimeoutException as exc:
- msg = f"Could not reach Pipelex API at {self.api_base_url} (timeout)"
- raise ApiUnreachableError(msg, api_url=self.api_base_url, code="ABORT_TIMEOUT") from exc
+ msg = f"Could not reach Pipelex API at {self.base_url} (timeout)"
+ raise ApiUnreachableError(msg, api_url=self.base_url, code="ABORT_TIMEOUT") from exc
except httpx.TransportError as exc:
code = type(exc).__name__
- msg = f"Could not reach Pipelex API at {self.api_base_url} ({code})"
- raise ApiUnreachableError(msg, api_url=self.api_base_url, code=code) from exc
+ msg = f"Could not reach Pipelex API at {self.base_url} ({code})"
+ raise ApiUnreachableError(msg, api_url=self.base_url, code=code) from exc
async def _request_product(self, method: str, endpoint: str, *, body: object | None = None) -> Any:
"""Issue a Pipelex-product request (`/v1/me`, `/v1/methods`, `/v1/billing/*`, …)
@@ -256,7 +266,7 @@ def _raise_api_response_error(self, *, method: str, endpoint: str, response: htt
msg = f"API {method} /{_API_PREFIX}/{endpoint} failed ({response.status_code}): {detail}"
raise ApiResponseError(
msg,
- api_url=self.api_base_url,
+ api_url=self.base_url,
status=response.status_code,
status_text=response.reason_phrase,
response_body=body_text,
@@ -277,9 +287,9 @@ def _raise_if_lifecycle_unavailable(self, response: httpx.Response, url: str) ->
msg = (
f"The durable run lifecycle is not available: {url} returned 404. Run polling is a "
f"hosted-API extension (/{_API_PREFIX}/{_RUNS}/*), not part of the MTHDS Protocol; "
- "PIPELEX_API_URL points at a bare runner that does not serve it."
+ "PIPELEX_BASE_URL points at a bare runner that does not serve it."
)
- raise RunLifecycleUnavailableError(msg, api_url=self.api_base_url)
+ raise RunLifecycleUnavailableError(msg, api_url=self.base_url)
# ── Protocol surface: `execute` override (gateway-timeout translation) ──
diff --git a/pipelex_sdk/errors.py b/pipelex_sdk/errors.py
index b7de7c5..05a5cc2 100644
--- a/pipelex_sdk/errors.py
+++ b/pipelex_sdk/errors.py
@@ -133,7 +133,7 @@ def __init__(self, message: str, run_id: str, timeout_seconds: float) -> None:
class RunLifecycleUnavailableError(PipelineRequestError):
"""Raised when the durable run lifecycle (`/v1/runs/*`) is not served by the
- configured `PIPELEX_API_URL`.
+ configured `PIPELEX_BASE_URL`.
Run polling is a hosted-API extension, not part of the MTHDS Protocol: the
open-source `pipelex-api` runner executes methods but has no run store, so it
diff --git a/pyproject.toml b/pyproject.toml
index a39fa9f..5654006 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "pipelex-sdk"
-version = "0.1.1"
+version = "0.2.0"
description = "The Python client for the Pipelex hosted API — the MTHDS Protocol surface plus the durable run lifecycle and the Pipelex product surface, built on the `mthds` protocol base."
authors = [{ name = "Evotis S.A.S.", email = "oss@pipelex.com" }]
maintainers = [{ name = "Pipelex staff", email = "oss@pipelex.com" }]
@@ -19,7 +19,7 @@ classifiers = [
]
dependencies = [
- "mthds>=0.6.1",
+ "mthds>=0.7.0",
"pydantic>=2.10.6,<3.0.0",
"backports.strenum>=1.3.0 ; python_version < '3.11'",
"typing-extensions>=4.0.0",
diff --git a/tests/unit/test_client_construction.py b/tests/unit/test_client_construction.py
index d51adaa..2fad2c2 100644
--- a/tests/unit/test_client_construction.py
+++ b/tests/unit/test_client_construction.py
@@ -8,7 +8,7 @@
from pipelex_sdk.client import PipelexAPIClient
-_MTHDS_DEFAULT_CREDENTIALS = {"api_key": "", "api_url": "https://api.pipelex.com", "runner": "api", "telemetry": "0"}
+_MTHDS_DEFAULT_CREDENTIALS = {"api_key": "", "base_url": "https://api.pipelex.com", "runner": "api"}
class TestClientConstruction:
@@ -16,61 +16,61 @@ class TestClientConstruction:
def _isolate_env(self, mocker: MockerFixture) -> None:
"""Hermetic construction — no real env vars, mthds resolver returns defaults."""
mocker.patch.dict(os.environ, {}, clear=True)
- mocker.patch("pipelex_sdk.client.load_credentials", return_value=dict(_MTHDS_DEFAULT_CREDENTIALS))
+ mocker.patch("pipelex_sdk.client.load_config", return_value=dict(_MTHDS_DEFAULT_CREDENTIALS))
def test_defaults_to_hosted_base_and_anonymous(self) -> None:
client = PipelexAPIClient()
- assert client.api_base_url == "https://api.pipelex.com"
+ assert client.base_url == "https://api.pipelex.com"
assert client.origin_url == "https://api.pipelex.com"
- assert client.api_token == ""
+ assert client.api_key == ""
def test_pipelex_env_takes_precedence_over_mthds(self, mocker: MockerFixture) -> None:
- mocker.patch.dict(os.environ, {"PIPELEX_API_KEY": "pk-live", "PIPELEX_API_URL": "http://localhost:8081"}, clear=True)
+ mocker.patch.dict(os.environ, {"PIPELEX_API_KEY": "pk-live", "PIPELEX_BASE_URL": "http://localhost:8081"}, clear=True)
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "mthds-key", "api_url": "https://mthds.example.com", "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "mthds-key", "base_url": "https://mthds.example.com", "runner": "api"},
)
client = PipelexAPIClient()
- assert client.api_token == "pk-live"
- assert client.api_base_url == "http://localhost:8081"
+ assert client.api_key == "pk-live"
+ assert client.base_url == "http://localhost:8081"
def test_falls_back_to_mthds_credentials(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "mthds-key", "api_url": "https://mthds.example.com", "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "mthds-key", "base_url": "https://mthds.example.com", "runner": "api"},
)
client = PipelexAPIClient()
- assert client.api_token == "mthds-key"
- assert client.api_base_url == "https://mthds.example.com"
+ assert client.api_key == "mthds-key"
+ assert client.base_url == "https://mthds.example.com"
def test_explicit_args_override_env_and_credentials(self, mocker: MockerFixture) -> None:
- mocker.patch.dict(os.environ, {"PIPELEX_API_KEY": "pk-env", "PIPELEX_API_URL": "http://env.example.com"}, clear=True)
- client = PipelexAPIClient(api_token="arg-token", api_base_url="https://arg.example.com")
- assert client.api_token == "arg-token"
- assert client.api_base_url == "https://arg.example.com"
+ mocker.patch.dict(os.environ, {"PIPELEX_API_KEY": "pk-env", "PIPELEX_BASE_URL": "http://env.example.com"}, clear=True)
+ client = PipelexAPIClient(api_key="arg-token", base_url="https://arg.example.com")
+ assert client.api_key == "arg-token"
+ assert client.base_url == "https://arg.example.com"
def test_explicit_empty_token_forces_anonymous_over_env(self, mocker: MockerFixture) -> None:
- """An explicit `api_token=""` means anonymous and must win over a configured env token."""
+ """An explicit `api_key=""` means anonymous and must win over a configured env token."""
mocker.patch.dict(os.environ, {"PIPELEX_API_KEY": "pk-env"}, clear=True)
- client = PipelexAPIClient(api_token="")
- assert client.api_token == ""
+ client = PipelexAPIClient(api_key="")
+ assert client.api_key == ""
def test_explicit_empty_token_forces_anonymous_over_credentials(self, mocker: MockerFixture) -> None:
- """An explicit `api_token=""` must win over an mthds credential token too."""
+ """An explicit `api_key=""` must win over an mthds credential token too."""
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "mthds-key", "api_url": "https://mthds.example.com", "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "mthds-key", "base_url": "https://mthds.example.com", "runner": "api"},
)
- client = PipelexAPIClient(api_token="")
- assert client.api_token == ""
+ client = PipelexAPIClient(api_key="")
+ assert client.api_key == ""
def test_strips_trailing_slash(self) -> None:
- client = PipelexAPIClient(api_base_url="https://api.pipelex.com/")
- assert client.api_base_url == "https://api.pipelex.com"
+ client = PipelexAPIClient(base_url="https://api.pipelex.com/")
+ assert client.base_url == "https://api.pipelex.com"
assert client.origin_url == "https://api.pipelex.com"
def test_origin_includes_port(self) -> None:
- client = PipelexAPIClient(api_base_url="http://localhost:8081")
+ client = PipelexAPIClient(base_url="http://localhost:8081")
assert client.origin_url == "http://localhost:8081"
@pytest.mark.parametrize(
@@ -87,4 +87,4 @@ def test_origin_includes_port(self) -> None:
)
def test_rejects_non_host_only_base_url(self, bad_url: str) -> None:
with pytest.raises(PipelineRequestError):
- PipelexAPIClient(api_base_url=bad_url)
+ PipelexAPIClient(base_url=bad_url)
diff --git a/tests/unit/test_client_execute.py b/tests/unit/test_client_execute.py
index 75d3e07..6e99adf 100644
--- a/tests/unit/test_client_execute.py
+++ b/tests/unit/test_client_execute.py
@@ -34,12 +34,12 @@ class TestClientExecute:
@pytest.fixture(autouse=True)
def _mock_credentials(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": "", "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": "", "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="test-token", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="test-token", base_url=_BASE_URL)
def test_gateway_503_past_ceiling_translates_to_timeout(self, mocker: MockerFixture) -> None:
client = self._client()
diff --git a/tests/unit/test_client_health.py b/tests/unit/test_client_health.py
index b9213da..23906b0 100644
--- a/tests/unit/test_client_health.py
+++ b/tests/unit/test_client_health.py
@@ -27,12 +27,12 @@ class TestClientHealth:
@pytest.fixture(autouse=True)
def _isolate(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": _BASE_URL, "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": _BASE_URL, "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="t", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="t", base_url=_BASE_URL)
def test_health_hits_origin_level_path_outside_v1(self, mocker: MockerFixture) -> None:
client = self._client()
diff --git a/tests/unit/test_client_lifecycle.py b/tests/unit/test_client_lifecycle.py
index 346915f..39280be 100644
--- a/tests/unit/test_client_lifecycle.py
+++ b/tests/unit/test_client_lifecycle.py
@@ -40,23 +40,23 @@ class TestClientLifecycle:
def _mock_credentials(self, mocker: MockerFixture) -> None:
"""Keep construction hermetic — never touch the real credentials file/env."""
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": "", "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": "", "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="test-token", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="test-token", base_url=_BASE_URL)
# ── start (inherited body-building + bare-runner 404 translation) ──
def test_start_targets_v1_url_and_returns_run_result_start(self, mocker: MockerFixture) -> None:
"""Start posts to /v1/start; a 202 parses into RunResultStart with the authoritative id."""
- client = PipelexAPIClient(api_token="t", api_base_url=f"{_BASE_URL}/")
+ client = PipelexAPIClient(api_key="t", base_url=f"{_BASE_URL}/")
body = {"pipeline_run_id": "run_1", "state": "RUNNING", "created_at": "2026-06-10T00:00:00Z"}
send_mock = mocker.patch.object(client, "_send", mocker.AsyncMock(return_value=_response(202, json=body)))
started = asyncio.run(client.start(pipe_code="answer"))
- assert client.api_base_url == _BASE_URL
+ assert client.base_url == _BASE_URL
assert send_mock.call_args.args[1] == f"{_BASE_URL}/v1/start"
assert started.pipeline_run_id == "run_1"
diff --git a/tests/unit/test_client_product.py b/tests/unit/test_client_product.py
index 57a2356..ea98eeb 100644
--- a/tests/unit/test_client_product.py
+++ b/tests/unit/test_client_product.py
@@ -51,12 +51,12 @@ class TestClientProduct:
@pytest.fixture(autouse=True)
def _isolate(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": _BASE_URL, "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": _BASE_URL, "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="test-token", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="test-token", base_url=_BASE_URL)
def _mock_send(self, mocker: MockerFixture, client: PipelexAPIClient, response: httpx.Response) -> MockType:
return mocker.patch.object(client, "_send", mocker.AsyncMock(return_value=response))
diff --git a/tests/unit/test_client_run_fallback.py b/tests/unit/test_client_run_fallback.py
index 3fb14f9..2072f73 100644
--- a/tests/unit/test_client_run_fallback.py
+++ b/tests/unit/test_client_run_fallback.py
@@ -45,12 +45,12 @@ class TestClientRunFallback:
@pytest.fixture(autouse=True)
def _mock_credentials(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": "", "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": "", "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="test-token", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="test-token", base_url=_BASE_URL)
# ── Hosted (durable start + poll) ────────────────────────────
diff --git a/tests/unit/test_client_transport.py b/tests/unit/test_client_transport.py
index fc6ec60..041566a 100644
--- a/tests/unit/test_client_transport.py
+++ b/tests/unit/test_client_transport.py
@@ -27,12 +27,12 @@ class TestClientTransport:
@pytest.fixture(autouse=True)
def _isolate(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": _BASE_URL, "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": _BASE_URL, "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="t", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="t", base_url=_BASE_URL)
# ── _request_product ─────────────────────────────────────────────
diff --git a/tests/unit/test_client_validate.py b/tests/unit/test_client_validate.py
index 6795a22..d0840c8 100644
--- a/tests/unit/test_client_validate.py
+++ b/tests/unit/test_client_validate.py
@@ -28,12 +28,12 @@ class TestClientValidate:
@pytest.fixture(autouse=True)
def _isolate(self, mocker: MockerFixture) -> None:
mocker.patch(
- "pipelex_sdk.client.load_credentials",
- return_value={"api_key": "", "api_url": _BASE_URL, "runner": "api", "telemetry": "0"},
+ "pipelex_sdk.client.load_config",
+ return_value={"api_key": "", "base_url": _BASE_URL, "runner": "api"},
)
def _client(self) -> PipelexAPIClient:
- return PipelexAPIClient(api_token="t", api_base_url=_BASE_URL)
+ return PipelexAPIClient(api_key="t", base_url=_BASE_URL)
def _mock_send(self, mocker: MockerFixture, client: PipelexAPIClient, *, json_body: object) -> MockType:
response = httpx.Response(200, json=json_body, request=httpx.Request("POST", f"{_BASE_URL}/v1/validate"))
diff --git a/uv.lock b/uv.lock
index 29599cc..9bb2138 100644
--- a/uv.lock
+++ b/uv.lock
@@ -250,7 +250,7 @@ wheels = [
[[package]]
name = "mthds"
-version = "0.6.1"
+version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backports-strenum", marker = "python_full_version < '3.11'" },
@@ -261,9 +261,9 @@ dependencies = [
{ name = "tomlkit" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a7/33/c274de1115b6cbe8ac3475327a622893070f1ca40d9f2d0172d6fcae62b9/mthds-0.6.1.tar.gz", hash = "sha256:3d6b93306708f0ef8971285cfab4d9a488fca364e5db7e63a7dba2d065070eef", size = 132504, upload-time = "2026-07-01T07:09:50.568Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/e6/fe/52bf1818f41b71658f24ee78dfe24730c04c1066e8b434f39fe23823cbb4/mthds-0.7.0.tar.gz", hash = "sha256:35e8c9d5f4b27e86fec02245f72bc2eb9c849790d39a8ffe6dcb001e7fe7d4cb", size = 136385, upload-time = "2026-07-02T20:17:58.875Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ff/52/0538d1bd11067f60f86ad74dc4d36bcad81ff3b549f71b3290aebd3a9aeb/mthds-0.6.1-py3-none-any.whl", hash = "sha256:5cd39d2b6fc5b43c5967162d80178b050748502515eda26916c2b80e8f3f1f42", size = 57705, upload-time = "2026-07-01T07:09:49.256Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/9f/f12c6de8a2ed9938a5914559639921ded8275f42b8c43ac1af35fe7a488f/mthds-0.7.0-py3-none-any.whl", hash = "sha256:4e371b73ba259899f5ba0b460809d00f176ac44ad773073a3ebb6c10ee207866", size = 57898, upload-time = "2026-07-02T20:17:57.525Z" },
]
[[package]]
@@ -350,7 +350,7 @@ wheels = [
[[package]]
name = "pipelex-sdk"
-version = "0.1.1"
+version = "0.2.0"
source = { editable = "." }
dependencies = [
{ name = "backports-strenum", marker = "python_full_version < '3.11'" },
@@ -375,7 +375,7 @@ dev = [
requires-dist = [
{ name = "backports-strenum", marker = "python_full_version < '3.11'", specifier = ">=1.3.0" },
{ name = "httpx", specifier = ">=0.23.0,<1.0.0" },
- { name = "mthds", specifier = ">=0.6.1" },
+ { name = "mthds", specifier = ">=0.7.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.19.1" },
{ name = "pydantic", specifier = ">=2.10.6,<3.0.0" },
{ name = "pylint", marker = "extra == 'dev'", specifier = "==4.0.4" },