feat(registry): implement async RegistryClient using httpx #32
@@ -11,6 +11,7 @@ and this project adheres to [Clever Semantic Versioning](https://www.w3.org/subm
|
||||
|
||||
- **`merge_configs()` public API** (`cleveractors.merge_configs`): New module-level function implementing the Actor Configuration Standard §3.1 deep-merge algorithm. Accepts an arbitrary number of `dict[str, Any]` arguments and returns a fresh merged dict without mutating any input. Merge semantics: absent key → add; both mappings → deep-merge recursively; both sequences → append; otherwise → replace. Zero-argument call returns `{}`. Exported from `cleveractors.__init__` and `__all__`.
|
||||
- **`validate_dict()` public API** (`cleveractors.validate_dict`): New router-facing validation function that validates a spec-conformant Actor Configuration Standard v1.0.0 dict against the full schema and enforces platform-level structural constraints. Validates top-level key presence (`agents`, `routes`), agent types, LLM provider allowlists, route/edge field names (`source`/`target` only — legacy `from`/`to` rejected), node types, stream operator types, and structural limits (`max_graph_depth`, `max_subgraph_depth`, `max_total_nodes`) from the supplied `platform_limits` dict. Returns the dict unchanged when valid; raises `ConfigurationError` on any violation. Pure static validator with no file I/O, env-var reads, or app construction. Exported from `cleveractors.__init__` and listed in `__all__`. (ADR-2024, ADR-2025, ADR-2029)
|
||||
- **Registry HTTP Client** (`cleveractors.registry`): Async registry client implementing the Package Registry Standard v1.0.0 HTTP API endpoints (§8) using `httpx.AsyncClient`. Supports all four endpoints: `GET /packages/{id}` (retrieve by PackageId), `GET /{type}/{ns}/{name}?version=` (reference resolution with version alias resolution per §4.2), `GET /browse` (discovery with type/namespace filters), and `GET /.well-known/cleverthis-packages` (registry metadata). Maps all 8 standard error types (§13.2) to typed exceptions via error-body type parsing with status-code fallback. Supports anonymous reads (§9.1) and optional API key authentication (§9.2). Includes async context manager support, 32 Behave BDD scenarios, 7 Robot Framework integration tests against a fake server, and ASV benchmarks. All registry modules achieve 100% coverage.
|
||||
- **Core CleverActors Framework**: New agent-based LLM orchestration framework implementing the Actor Configuration Standard (§1-4). Includes agent base class, factory pattern for agent creation, configuration management, template rendering engine, and exception hierarchy.
|
||||
- **LLM Agent** (`type: llm`, §4.4): Agent backed by language models with support for OpenAI, Anthropic, and Google Gemini providers. Configurable temperature, max_tokens, system prompts, memory/history, and structured output (json_mode/response_format).
|
||||
- **Tool Agent** (`type: tool`, §4.5): Deterministic agent executing built-in tools (echo, math, json_parse, http_request, file_read, file_write, progress_bar) and custom inline code tools. Supports safe/unsafe execution modes, shell command filtering, and file operation sandboxing.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""ASV benchmarks for the Registry HTTP client.
|
||||
|
||||
Measures performance of the RegistryClient against a fake registry server
|
||||
for the four API endpoints.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from cleveractors.registry.client import RegistryClient
|
||||
|
||||
|
||||
class RegistryClientBenchmark:
|
||||
"""Benchmark suite for RegistryClient."""
|
||||
|
||||
params: list[str] = ["actor", "template", "skill"]
|
||||
|
||||
def setup(self) -> None:
|
||||
self.client = RegistryClient(
|
||||
base_url="http://127.0.0.1:9199",
|
||||
timeout=5.0,
|
||||
)
|
||||
self.fake_package_id = "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
|
||||
|
||||
def teardown(self) -> None:
|
||||
async def _close() -> None:
|
||||
await self.client.close()
|
||||
|
||||
try:
|
||||
asyncio.get_event_loop().run_until_complete(_close())
|
||||
except RuntimeError:
|
||||
asyncio.run(_close())
|
||||
|
||||
def time_get_package(self) -> None:
|
||||
async def _bench() -> None:
|
||||
await self.client.get_package(self.fake_package_id)
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
future = asyncio.ensure_future(_bench(), loop=loop)
|
||||
loop.run_until_complete(future)
|
||||
else:
|
||||
loop.run_until_complete(_bench())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def time_resolve_package(self, pkg_type: str) -> None:
|
||||
async def _bench() -> None:
|
||||
await self.client.resolve_package(pkg_type, "example", "test", "v1.0.0")
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
future = asyncio.ensure_future(_bench(), loop=loop)
|
||||
loop.run_until_complete(future)
|
||||
else:
|
||||
loop.run_until_complete(_bench())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def time_browse(self) -> None:
|
||||
async def _bench() -> None:
|
||||
await self.client.browse()
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
future = asyncio.ensure_future(_bench(), loop=loop)
|
||||
loop.run_until_complete(future)
|
||||
else:
|
||||
loop.run_until_complete(_bench())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def time_discover(self) -> None:
|
||||
async def _bench() -> None:
|
||||
await self.client.discover()
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
future = asyncio.ensure_future(_bench(), loop=loop)
|
||||
loop.run_until_complete(future)
|
||||
else:
|
||||
loop.run_until_complete(_bench())
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,200 @@
|
||||
Feature: Registry HTTP Client
|
||||
|
||||
As a developer using the Package Registry Standard v1.0.0,
|
||||
I want an async HTTP client that can retrieve packages, resolve references,
|
||||
browse published packages, and discover registry metadata,
|
||||
so that I can interact with any compliant registry server.
|
||||
|
||||
Background:
|
||||
Given a clean test environment for registry HTTP client
|
||||
|
||||
# ── Client Initialisation ────────────────────────────────────────────
|
||||
|
||||
Scenario: Create client with base URL
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
Then the client base URL should be "https://registry.example.com"
|
||||
|
||||
Scenario: Create client with optional API key
|
||||
When I create a RegistryClient with base URL "https://registry.example.com" and API key "secret-token"
|
||||
Then the client should have the API key "secret-token"
|
||||
|
||||
Scenario: Create client with empty base URL raises ValueError
|
||||
When I try to create a RegistryClient with empty base URL
|
||||
Then a ValueError should be raised by the registry client
|
||||
|
||||
# ── GET /packages/{package_id} (§8.2.1) ──────────────────────────────
|
||||
|
||||
Scenario: Fetch package by valid PackageId returns YAML content
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"content": "name: test-pkg\ntype: actor"}'
|
||||
When I call get_package with package_id "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
|
||||
Then the registry result body equals '{"content": "name: test-pkg\ntype: actor"}'
|
||||
|
||||
Scenario: Fetch package returns 404 raises PackageNotFoundError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 404 with body '{"message": "Package not found"}'
|
||||
When I try to call get_package with package_id "pkg_act_deadbeef000000000000000000000000000000"
|
||||
Then a PackageNotFoundError should be raised
|
||||
|
||||
Scenario: Fetch package returns 400 raises InvalidPackageIdError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 400 with body '{"message": "Invalid Package ID"}'
|
||||
When I try to call get_package with package_id "invalid_id"
|
||||
Then an InvalidPackageIdError should be raised
|
||||
|
||||
# ── GET /{type}/{ns}/{name}?version= (§8.2.2) ────────────────────────
|
||||
|
||||
Scenario: Resolve package with concrete version
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef", "type": "actor"}'
|
||||
When I call resolve_package with type "actor", namespace "example", name "my-actor", version "v1.0.0"
|
||||
Then the resolved package_id should be "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
|
||||
|
||||
Scenario: Resolve package with latest alias
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef", "type": "actor"}'
|
||||
When I call resolve_package with type "actor", namespace "example", name "my-actor" and version "latest"
|
||||
Then the resolved package_id should be "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
|
||||
|
||||
Scenario: Resolve package without version defaults to latest
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef", "type": "actor"}'
|
||||
When I call resolve_package with type "actor", namespace "example", name "my-actor" without version
|
||||
Then the resolved package_id should be "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
|
||||
|
||||
Scenario: Resolve package returns 404 raises PackageNotFoundError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 404 with body '{"message": "Not found"}'
|
||||
When I try to call resolve_package with type "actor", namespace "example", name "missing", version "v1.0.0"
|
||||
Then a PackageNotFoundError should be raised
|
||||
|
||||
# ── GET /browse (§8.4.1) ─────────────────────────────────────────────
|
||||
|
||||
Scenario: Browse all packages without filters
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"packages": [], "count": 0}'
|
||||
When I call browse without filters
|
||||
Then the result should have "packages" key and "count" key
|
||||
|
||||
Scenario: Browse packages filtered by type
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"packages": [{"id": "pkg_act_abc", "type": "actor", "name": "test"}], "count": 1}'
|
||||
When I call browse with type "actor"
|
||||
Then the result count should be 1
|
||||
|
||||
Scenario: Browse packages filtered by namespace
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"packages": [], "count": 0}'
|
||||
When I call browse with namespace "example"
|
||||
Then the result count should be 0
|
||||
|
||||
# ── GET /.well-known/cleverthis-packages (§8.4.2) ────────────────────
|
||||
|
||||
Scenario: Discover registry metadata
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"name": "Test Registry", "version": "1.0.0", "supported_types": ["actor"], "authentication": ["anonymous"], "features": ["semantic_versioning"]}'
|
||||
When I call discover
|
||||
Then the result should have key "name" with value "Test Registry"
|
||||
Then the result should have key "supported_types"
|
||||
|
||||
# ── Network errors ───────────────────────────────────────────────────
|
||||
|
||||
Scenario: Connection timeout raises RegistryNetworkError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server raises a timeout error
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then a RegistryNetworkError should be raised
|
||||
|
||||
# ── Error mapping (§13.2) ─────────────────────────────────────────────
|
||||
|
||||
Scenario: 401 response raises AuthenticationRequiredError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 401 with body '{"message": "Authentication required"}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then an AuthenticationRequiredError should be raised
|
||||
|
||||
Scenario: 403 response raises AccessDeniedError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 403 with body '{"message": "Access denied"}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then an AccessDeniedError should be raised
|
||||
|
||||
Scenario: 409 response raises ConflictError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 409 with body '{"message": "Package ID conflict"}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then a ConflictError should be raised
|
||||
|
||||
Scenario: 500 response raises InternalServerError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 500 with body '{"message": "Internal error"}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then an InternalServerError should be raised
|
||||
|
||||
Scenario: 500 range response raises InternalServerError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 503 with body '{"message": "Service unavailable"}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then an InternalServerError should be raised
|
||||
|
||||
Scenario: Unknown error status falls back to RegistryError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 418 with non-JSON body "plain text error"
|
||||
|
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then a RegistryError should be raised
|
||||
|
||||
Scenario: Error response with non-JSON body maps by status
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 400 with non-JSON body "plain text error"
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then an InvalidPackageIdError should be raised
|
||||
|
||||
# ── Error type parsing from body (§13.1) ───────────────────────────────
|
||||
|
||||
Scenario: 404 with VersionNotFound error type in body raises VersionNotFoundError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 404 with body '{"error": {"type": "VersionNotFound", "message": "Version v99.0.0 not found"}}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then a VersionNotFoundError should be raised
|
||||
|
||||
Scenario: 400 with ValidationError error type in body raises ValidationError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "Package validation failed"}}'
|
||||
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
|
||||
Then a ValidationError should be raised
|
||||
|
||||
Scenario: 404 without error type in body falls back to PackageNotFoundError
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 404 with body '{"message": "Package not found"}'
|
||||
|
||||
Scenario: Client with API key includes authorization header
|
||||
When I create an authenticated RegistryClient pointed at "https://registry.example.com" with API key "my-api-key"
|
||||
When the mock server returns status 200 with body '{"content": "ok"}'
|
||||
When I call get_package with API key client and package_id "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
|
||||
Then the registry result body equals '{"content": "ok"}'
|
||||
|
||||
# ── All package type prefixes ────────────────────────────────────────
|
||||
|
||||
Scenario Outline: Resolve supports all package types
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When the mock server returns status 200 with body '{"package_id": "pkg_act_test00000000000000000000000000000000000", "type": "actor"}'
|
||||
When I call resolve_package with type "<type>", namespace "example", name "test", version "v1.0.0"
|
||||
Then the resolved package_id should be "pkg_act_test00000000000000000000000000000000000"
|
||||
|
||||
Examples:
|
||||
| type |
|
||||
| actor |
|
||||
| graph |
|
||||
| stream |
|
||||
| agent |
|
||||
| template |
|
||||
| skill |
|
||||
| mcp |
|
||||
| lsp |
|
||||
|
||||
# ── Context manager ──────────────────────────────────────────────────
|
||||
|
||||
Scenario: Client can be used as async context manager
|
||||
When I create a RegistryClient pointed at "https://registry.example.com"
|
||||
When I use the client as an async context manager
|
||||
Then the client should be closed after the context block
|
||||
@@ -0,0 +1,429 @@
|
||||
"""
|
||||
Step definitions for Registry HTTP Client BDD tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Optional
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import httpx
|
||||
from behave import given, then, when
|
||||
|
||||
from cleveractors.registry.client import RegistryClient
|
||||
from cleveractors.registry.exceptions import (
|
||||
AccessDeniedError,
|
||||
AuthenticationRequiredError,
|
||||
ConflictError,
|
||||
InternalServerError,
|
||||
InvalidPackageIdError,
|
||||
PackageNotFoundError,
|
||||
RegistryError,
|
||||
RegistryNetworkError,
|
||||
ValidationError,
|
||||
VersionNotFoundError,
|
||||
)
|
||||
|
||||
|
||||
@given("a clean test environment for registry HTTP client")
|
||||
def step_clean_environment_http_client(context: Any) -> None:
|
||||
context.client = None
|
||||
context.result = None
|
||||
context.error = None
|
||||
context._http_mock = None
|
||||
|
||||
|
||||
# ── Client Initialisation ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@when('I create a RegistryClient pointed at "{base_url}"')
|
||||
def step_create_client(context: Any, base_url: str) -> None:
|
||||
context.client = RegistryClient(base_url=base_url)
|
||||
|
||||
|
||||
@when('I create a RegistryClient with base URL "{base_url}" and API key "{api_key}"')
|
||||
def step_create_client_with_key(context: Any, base_url: str, api_key: str) -> None:
|
||||
context.client = RegistryClient(base_url=base_url, api_key=api_key)
|
||||
|
||||
|
||||
@when("I try to create a RegistryClient with empty base URL")
|
||||
def step_create_client_empty_url(context: Any) -> None:
|
||||
try:
|
||||
RegistryClient(base_url="")
|
||||
except ValueError as exc:
|
||||
context.error = exc
|
||||
|
||||
|
||||
@then('the client base URL should be "{expected}"')
|
||||
def step_check_base_url(context: Any, expected: str) -> None:
|
||||
assert context.client is not None
|
||||
assert context.client.base_url == expected.rstrip("/")
|
||||
|
||||
|
||||
@then('the client should have the API key "{expected}"')
|
||||
def step_check_api_key(context: Any, expected: str) -> None:
|
||||
assert context.client is not None
|
||||
assert context.client.api_key == expected
|
||||
|
||||
|
||||
@then("a ValueError should be raised by the registry client")
|
||||
def step_check_value_error_registry(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, ValueError)
|
||||
|
||||
|
||||
# ── Mock server helpers ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@then('the result should have "{key1}" key and "{key2}" key')
|
||||
def step_check_two_keys(context: Any, key1: str, key2: str) -> None:
|
||||
assert context.result is not None
|
||||
assert key1 in context.result
|
||||
assert key2 in context.result
|
||||
|
||||
|
||||
@then('the result should have key "{key}" with value "{value}"')
|
||||
def step_check_key_value(context: Any, key: str, value: str) -> None:
|
||||
assert context.result is not None
|
||||
assert context.result.get(key) == value
|
||||
|
||||
|
||||
def _set_mock_response(context: Any, status: int, body_json: str) -> None:
|
||||
import json
|
||||
|
||||
body = json.loads(body_json)
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = status
|
||||
mock_response.json.return_value = body
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
if status >= 400:
|
||||
exc = httpx.HTTPStatusError(
|
||||
"error", request=MagicMock(), response=mock_response
|
||||
)
|
||||
mock_response.raise_for_status.side_effect = exc
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get.return_value = mock_response
|
||||
mock_client.is_closed = False
|
||||
mock_client.aclose = AsyncMock()
|
||||
|
||||
context._mock_client = mock_client
|
||||
context._mock_response = mock_response
|
||||
|
||||
|
||||
def _set_mock_non_json_response(context: Any, status: int, body_text: str) -> None:
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = status
|
||||
mock_response.text = body_text
|
||||
mock_response.json.side_effect = ValueError("not JSON")
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
exc = httpx.HTTPStatusError("error", request=MagicMock(), response=mock_response)
|
||||
mock_response.raise_for_status.side_effect = exc
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get.return_value = mock_response
|
||||
mock_client.is_closed = False
|
||||
mock_client.aclose = AsyncMock()
|
||||
|
||||
context._mock_client = mock_client
|
||||
context._mock_response = mock_response
|
||||
|
||||
|
||||
def _set_mock_timeout(context: Any) -> None:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.is_closed = False
|
||||
mock_client.aclose = AsyncMock()
|
||||
mock_client.get.side_effect = httpx.ReadTimeout("timed out")
|
||||
context._mock_client = mock_client
|
||||
|
||||
|
||||
@when("the mock server returns status {status:d} with body '{body}'")
|
||||
def step_mock_server_response(context: Any, status: int, body: str) -> None:
|
||||
_set_mock_response(context, status, body)
|
||||
|
||||
|
||||
@when("the mock server raises a timeout error")
|
||||
def step_mock_server_timeout(context: Any) -> None:
|
||||
_set_mock_timeout(context)
|
||||
|
||||
|
||||
@when('the mock server returns status {status:d} with non-JSON body "{body_text}"')
|
||||
def step_mock_server_non_json(context: Any, status: int, body_text: str) -> None:
|
||||
_set_mock_non_json_response(context, status, body_text)
|
||||
|
||||
|
||||
# ── get_package (§8.2.1) ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@when('I call get_package with package_id "{package_id}"')
|
||||
def step_call_get_package(context: Any, package_id: str) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.get_package(package_id)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@when('I try to call get_package with package_id "{package_id}"')
|
||||
def step_try_call_get_package(context: Any, package_id: str) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
try:
|
||||
await context.client.get_package(package_id)
|
||||
except Exception as exc:
|
||||
context.error = exc
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@then("the registry result body equals '{body}'")
|
||||
def step_check_result_body(context: Any, body: str) -> None:
|
||||
import json
|
||||
|
||||
expected = json.loads(body)
|
||||
assert context.result == expected
|
||||
|
||||
|
||||
@then("a PackageNotFoundError should be raised")
|
||||
def step_check_package_not_found(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, PackageNotFoundError)
|
||||
|
||||
|
||||
@then("an InvalidPackageIdError should be raised")
|
||||
def step_check_invalid_id(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, InvalidPackageIdError)
|
||||
|
||||
|
||||
@then("a RegistryNetworkError should be raised")
|
||||
def step_check_network_error(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, RegistryNetworkError)
|
||||
|
||||
|
||||
# ── resolve_package (§8.2.2) ────────────────────────────────────────────
|
||||
|
||||
|
||||
@when(
|
||||
'I call resolve_package with type "{pkg_type}", namespace "{ns}", name "{name}", version "{version}"'
|
||||
)
|
||||
def step_call_resolve_with_version(
|
||||
context: Any, pkg_type: str, ns: str, name: str, version: str
|
||||
) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.resolve_package(
|
||||
pkg_type, ns, name, version
|
||||
)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@when(
|
||||
'I call resolve_package with type "{pkg_type}", namespace "{ns}", name "{name}" and version "{version}"'
|
||||
)
|
||||
def step_call_resolve_and_version(
|
||||
context: Any, pkg_type: str, ns: str, name: str, version: str
|
||||
) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.resolve_package(
|
||||
pkg_type, ns, name, version
|
||||
)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@when(
|
||||
'I call resolve_package with type "{pkg_type}", namespace "{ns}", name "{name}" without version'
|
||||
)
|
||||
def step_call_resolve_no_version(
|
||||
context: Any, pkg_type: str, ns: str, name: str
|
||||
) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.resolve_package(pkg_type, ns, name)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@when(
|
||||
'I try to call resolve_package with type "{pkg_type}", namespace "{ns}", name "{name}", version "{version}"'
|
||||
)
|
||||
def step_try_call_resolve(
|
||||
context: Any, pkg_type: str, ns: str, name: str, version: str
|
||||
) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
try:
|
||||
await context.client.resolve_package(pkg_type, ns, name, version)
|
||||
except Exception as exc:
|
||||
context.error = exc
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@then('the resolved package_id should be "{expected}"')
|
||||
def step_check_resolved_id(context: Any, expected: str) -> None:
|
||||
assert context.result is not None
|
||||
assert context.result.get("package_id") == expected
|
||||
|
||||
|
||||
# ── browse (§8.4.1) ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@then('the result should have "packages" key')
|
||||
def step_check_browse_package_key(context: Any) -> None:
|
||||
assert context.result is not None
|
||||
assert "packages" in context.result
|
||||
assert "count" in context.result
|
||||
|
||||
|
||||
@when("I call browse without filters")
|
||||
def step_call_browse_no_filters(context: Any) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.browse()
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@when('I call browse with type "{pkg_type}"')
|
||||
def step_call_browse_with_type(context: Any, pkg_type: str) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.browse(package_type=pkg_type)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@when('I call browse with namespace "{ns}"')
|
||||
def step_call_browse_with_namespace(context: Any, ns: str) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.browse(namespace=ns)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@then("the result count should be {expected:d}")
|
||||
def step_check_result_count(context: Any, expected: int) -> None:
|
||||
assert context.result is not None
|
||||
assert context.result.get("count") == expected
|
||||
|
||||
|
||||
# ── discover (§8.4.2) ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
@when("I call discover")
|
||||
def step_call_discover(context: Any) -> None:
|
||||
async def _call() -> None:
|
||||
context.client._client = context._mock_client
|
||||
context.result = await context.client.discover()
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
|
||||
|
||||
@then('the result should have key "{key}"')
|
||||
def step_check_key_present(context: Any, key: str) -> None:
|
||||
assert context.result is not None
|
||||
assert key in context.result
|
||||
|
||||
|
||||
# ── Context manager ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@when("I use the client as an async context manager")
|
||||
def step_use_context_manager(context: Any) -> None:
|
||||
async def _use() -> None:
|
||||
context.ctx_clean_exit = True
|
||||
async with context.client as client:
|
||||
context.ctx_client = client
|
||||
context.ctx_was_open = not client._client.is_closed
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_use())
|
||||
|
||||
|
||||
@then("the client should be closed after the context block")
|
||||
def step_check_closed(context: Any) -> None:
|
||||
assert context.ctx_was_open
|
||||
assert context.ctx_clean_exit
|
||||
|
||||
|
||||
# ── Additional error type assertions ──────────────────────────────────
|
||||
|
||||
|
||||
@when(
|
||||
'I create an authenticated RegistryClient pointed at "{base_url}" with API key "{api_key}"'
|
||||
)
|
||||
def step_create_client_pointed_at_with_key(
|
||||
context: Any, base_url: str, api_key: str
|
||||
) -> None:
|
||||
context.client = RegistryClient(base_url=base_url, api_key=api_key)
|
||||
|
||||
|
||||
@then("an AuthenticationRequiredError should be raised")
|
||||
def step_check_auth_required(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, AuthenticationRequiredError)
|
||||
|
||||
|
||||
@then("an AccessDeniedError should be raised")
|
||||
def step_check_access_denied(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, AccessDeniedError)
|
||||
|
||||
|
||||
@then("a ConflictError should be raised")
|
||||
def step_check_conflict(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, ConflictError)
|
||||
|
||||
|
||||
@then("an InternalServerError should be raised")
|
||||
def step_check_internal_error(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, InternalServerError)
|
||||
|
||||
|
||||
@then("a RegistryError should be raised")
|
||||
def step_check_registry_error(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, RegistryError)
|
||||
assert not isinstance(
|
||||
context.error,
|
||||
(
|
||||
AuthenticationRequiredError,
|
||||
AccessDeniedError,
|
||||
ConflictError,
|
||||
InternalServerError,
|
||||
InvalidPackageIdError,
|
||||
PackageNotFoundError,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@then("a VersionNotFoundError should be raised")
|
||||
def step_check_version_not_found(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, VersionNotFoundError)
|
||||
|
||||
|
||||
@then("a ValidationError should be raised")
|
||||
def step_check_validation_error(context: Any) -> None:
|
||||
assert context.error is not None
|
||||
assert isinstance(context.error, ValidationError)
|
||||
|
||||
|
||||
@when('I call get_package with API key client and package_id "{package_id}"')
|
||||
def step_call_get_package_with_key(context: Any, package_id: str) -> None:
|
||||
async def _call() -> None:
|
||||
with patch("cleveractors.registry.client.httpx.AsyncClient") as mock_cls:
|
||||
mock_cls.return_value = context._mock_client
|
||||
context.result = await context.client.get_package(package_id)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_call())
|
||||
@@ -36,6 +36,7 @@ dependencies = [
|
||||
"pyyaml>=6.0.3",
|
||||
"structlog>=24.4.0",
|
||||
"aiohttp>=3.13.4",
|
||||
"httpx>=0.27.0",
|
||||
"RestrictedPython>=7.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Robot Framework keyword library for RegistryClient integration tests.
|
||||
|
||||
Provides keywords for testing the httpx-based RegistryClient against
|
||||
a fake registry server (started by the test suite).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Optional
|
||||
|
||||
from cleveractors.registry.client import RegistryClient
|
||||
from cleveractors.registry.exceptions import (
|
||||
PackageNotFoundError,
|
||||
RegistryNetworkError,
|
||||
)
|
||||
|
||||
ROBOT_LIBRARY_SCOPE = "TEST SUITE" # pragma: no cover - integration test library
|
||||
|
||||
|
||||
class RegistryClientLib: # pragma: no cover - integration test library
|
||||
"""Keyword library for RegistryClient Robot Framework integration tests."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._client: Optional[RegistryClient] = None
|
||||
self._result: Any = None
|
||||
self._error: Optional[Exception] = None
|
||||
|
||||
def create_registry_client(self, base_url: str) -> None:
|
||||
self._client = RegistryClient(base_url=base_url)
|
||||
|
||||
def create_registry_client_with_key(self, base_url: str, api_key: str) -> None:
|
||||
self._client = RegistryClient(base_url=base_url, api_key=api_key)
|
||||
|
||||
def get_package_by_id(self, package_id: str) -> None:
|
||||
async def _call() -> None:
|
||||
self._result = await self._client.get_package(package_id)
|
||||
|
||||
self._run(_call)
|
||||
|
||||
def resolve_package_ref(
|
||||
self,
|
||||
package_type: str,
|
||||
namespace: str,
|
||||
name: str,
|
||||
version: str = "",
|
||||
) -> None:
|
||||
async def _call() -> None:
|
||||
ver: Optional[str] = version if version else None
|
||||
self._result = await self._client.resolve_package(
|
||||
package_type, namespace, name, ver
|
||||
)
|
||||
|
||||
self._run(_call)
|
||||
|
||||
def browse_packages(self) -> None:
|
||||
async def _call() -> None:
|
||||
self._result = await self._client.browse()
|
||||
|
||||
self._run(_call)
|
||||
|
||||
def browse_packages_by_type(self, package_type: str) -> None:
|
||||
async def _call() -> None:
|
||||
self._result = await self._client.browse(package_type=package_type)
|
||||
|
||||
self._run(_call)
|
||||
|
||||
def discover_registry(self) -> None:
|
||||
async def _call() -> None:
|
||||
self._result = await self._client.discover()
|
||||
|
||||
self._run(_call)
|
||||
|
||||
def result_has_key(self, key: str) -> None:
|
||||
if key not in self._result:
|
||||
raise AssertionError(
|
||||
f"Result missing key {key!r}: {sorted(self._result.keys())}"
|
||||
)
|
||||
|
||||
def result_key_equals(self, key: str, expected: str) -> None:
|
||||
actual = str(self._result.get(key))
|
||||
if actual != expected:
|
||||
raise AssertionError(f"Key {key}: expected {expected!r}, got {actual!r}")
|
||||
|
||||
def result_count_equals(self, expected: str) -> None:
|
||||
actual = len(self._result.get("packages", []))
|
||||
if str(actual) != expected:
|
||||
raise AssertionError(f"Count: expected {expected!r}, got {actual!r}")
|
||||
|
||||
def get_package_should_raise_not_found(self, package_id: str) -> None:
|
||||
async def _call() -> None:
|
||||
try:
|
||||
await self._client.get_package(package_id)
|
||||
except PackageNotFoundError as exc:
|
||||
self._error = exc
|
||||
|
||||
self._run(_call)
|
||||
if self._error is None:
|
||||
raise AssertionError(
|
||||
f"Expected PackageNotFoundError for {package_id!r}, none raised"
|
||||
)
|
||||
|
||||
def get_package_should_raise_network_error(self, package_id: str) -> None:
|
||||
async def _call() -> None:
|
||||
try:
|
||||
await self._client.get_package(package_id)
|
||||
except RegistryNetworkError as exc:
|
||||
self._error = exc
|
||||
|
||||
self._run(_call)
|
||||
if self._error is None:
|
||||
raise AssertionError(
|
||||
f"Expected RegistryNetworkError for {package_id!r}, none raised"
|
||||
)
|
||||
|
||||
def close_client(self) -> None:
|
||||
async def _call() -> None:
|
||||
await self._client.close()
|
||||
|
||||
self._run(_call)
|
||||
|
||||
@staticmethod
|
||||
def _run(coro_fn: Any, timeout: float = 30.0) -> None:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
future = asyncio.run_coroutine_threadsafe(coro_fn(), loop)
|
||||
future.result(timeout=timeout)
|
||||
else:
|
||||
loop.run_until_complete(asyncio.wait_for(coro_fn(), timeout=timeout))
|
||||
except RuntimeError:
|
||||
asyncio.run(asyncio.wait_for(coro_fn(), timeout=timeout))
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Fake Package Registry server for integration testing.
|
||||
|
||||
Implements minimal responses for the 4 Package Registry Standard
|
||||
endpoints. Designed to be started as a subprocess by Robot Framework.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import socketserver
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
from typing import Any
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
|
||||
class FakeRegistryHandler(SimpleHTTPRequestHandler):
|
||||
"""Minimal handler implementing the Package Registry Standard endpoints."""
|
||||
|
||||
EXAMPLE_PACKAGES: list[dict[str, Any]] = [
|
||||
{
|
||||
"id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef",
|
||||
"type": "actor",
|
||||
"name": "test-actor",
|
||||
"description": "A test actor package for integration testing",
|
||||
"namespace": "example",
|
||||
"version_count": 3,
|
||||
"created_at": "2026-01-01T00:00:00Z",
|
||||
},
|
||||
{
|
||||
"id": "pkg_grh_0000000000000000000000000000000000000000",
|
||||
"type": "graph",
|
||||
"name": "test-graph",
|
||||
"description": "A test graph package",
|
||||
"namespace": "example",
|
||||
"version_count": 1,
|
||||
"created_at": "2026-02-01T00:00:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
def do_GET(self) -> None:
|
||||
parsed = urlparse(self.path)
|
||||
path = parsed.path
|
||||
params = parse_qs(parsed.query)
|
||||
|
||||
if path == "/.well-known/cleverthis-packages":
|
||||
self._json(
|
||||
200,
|
||||
{
|
||||
"name": "Fake Package Registry",
|
||||
"version": "1.0.0",
|
||||
"supported_types": ["actor", "graph", "stream", "agent"],
|
||||
"authentication": ["anonymous"],
|
||||
"features": ["semantic_versioning", "mutable_aliases"],
|
||||
},
|
||||
)
|
||||
elif path.startswith("/packages/"):
|
||||
package_id = path[len("/packages/") :]
|
||||
if package_id.startswith("pkg_act_a1b2c3d4e5"):
|
||||
self._json(
|
||||
200,
|
||||
{
|
||||
"content": "name: test-pkg\ntype: actor",
|
||||
"package_id": package_id,
|
||||
},
|
||||
)
|
||||
else:
|
||||
|
brent.edwards marked this conversation as resolved
Outdated
brent.edwards
commented
This is probably not a good response. There are packages returned in the earlier parts of this A generic testing framework would want examples of packages here. It would read the example packages, then try to read them. This is probably not a good response. There are packages returned in the earlier parts of this `if` statement. But nothing is listed here.
A generic testing framework would want examples of packages here. It would read the example packages, then try to read them.
CoreRasurae
commented
Fixed. fake_registry_server.py now defines EXAMPLE_PACKAGES with 2 packages (an actor and a graph) that are returned by the /browse endpoint. The browse response also supports optional ?type= filtering. Fixed. fake_registry_server.py now defines EXAMPLE_PACKAGES with 2 packages (an actor and a graph) that are returned by the /browse endpoint. The browse response also supports optional ?type= filtering.
|
||||
self._json(404, {"message": "Package not found"})
|
||||
elif path.startswith("/actor/") or path.startswith("/graph/"):
|
||||
parts = path.strip("/").split("/")
|
||||
if len(parts) >= 3:
|
||||
self._json(
|
||||
200,
|
||||
{
|
||||
"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef",
|
||||
"type": parts[0],
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._json(404, {"message": "Not found"})
|
||||
elif path == "/browse":
|
||||
packages = self.EXAMPLE_PACKAGES
|
||||
query_type = params.get("type", [None])[0]
|
||||
if query_type:
|
||||
packages = [p for p in packages if p["type"] == query_type]
|
||||
self._json(
|
||||
200,
|
||||
{
|
||||
"packages": packages,
|
||||
"count": len(packages),
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._json(404, {"message": "Not found"})
|
||||
|
||||
def _json(self, status: int, data: dict[str, Any]) -> None:
|
||||
body = json.dumps(data).encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, format: str, *args: Any) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def run_server(host: str = "127.0.0.1", port: int = 9191) -> None:
|
||||
server = HTTPServer((host, port), FakeRegistryHandler)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 9191
|
||||
run_server(host, port)
|
||||
@@ -0,0 +1,71 @@
|
||||
*** Settings ***
|
||||
Documentation Integration tests for the Registry HTTP client against a fake server.
|
||||
Library RegistryClientLib.py
|
||||
Library Process
|
||||
Library OperatingSystem
|
||||
Suite Setup Start Fake Registry Server
|
||||
Suite Teardown Stop Fake Registry Server
|
||||
|
||||
*** Variables ***
|
||||
${FAKE_SERVER_HOST} 127.0.0.1
|
||||
${FAKE_SERVER_PORT} ${9191}
|
||||
|
||||
*** Keywords ***
|
||||
Start Fake Registry Server
|
||||
[Documentation] Launch a fake registry server as a subprocess.
|
||||
Start Process
|
||||
... python robot${/}fake_registry_server.py ${FAKE_SERVER_HOST} ${FAKE_SERVER_PORT}
|
||||
... alias=fake_registry
|
||||
Sleep 0.5s
|
||||
${server_url}= Set Variable http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}
|
||||
Set Suite Variable ${SERVER_URL} ${server_url}
|
||||
|
||||
Stop Fake Registry Server
|
||||
[Documentation] Stop the fake server subprocess.
|
||||
Terminate Process fake_registry kill=${True}
|
||||
|
||||
*** Test Cases ***
|
||||
Get Package By Valid ID Returns Content
|
||||
[Documentation] GET /packages/{id} — valid PackageId returns content.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Get Package By Id pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef
|
||||
Result Has Key package_id
|
||||
|
||||
Get Package With Invalid ID Raises Not Found
|
||||
[Documentation] GET /packages/{id} — invalid ID returns 404.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Get Package Should Raise Not Found pkg_act_deadbeef000000000000000000000000000000
|
||||
|
||||
Resolve Package Returns Package ID
|
||||
[Documentation] GET /{type}/{ns}/{name} resolves to a concrete ID.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Resolve Package Ref actor example my-actor v1.0.0
|
||||
Result Has Key package_id
|
||||
Result Key Equals type actor
|
||||
|
||||
Browse Packages Returns List
|
||||
[Documentation] GET /browse returns package list and count.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Browse Packages
|
||||
Result Has Key packages
|
||||
Result Has Key count
|
||||
|
||||
Browse Packages By Type Returns Filtered Results
|
||||
[Documentation] GET /browse?type=actor filters results.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Browse Packages By Type actor
|
||||
Result Has Key packages
|
||||
Result Has Key count
|
||||
|
||||
Discover Registry Metadata
|
||||
[Documentation] GET /.well-known/cleverthis-packages returns registry info.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Discover Registry
|
||||
Result Has Key name
|
||||
Result Has Key version
|
||||
Result Has Key supported_types
|
||||
|
||||
Registry Client Closes Cleanly
|
||||
[Documentation] close() shuts down the underlying httpx client.
|
||||
Create Registry Client ${SERVER_URL}
|
||||
Close Client
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Package Registry client implementing the Package Registry Standard v1.0.0."""
|
||||
|
||||
from cleveractors.registry.client import RegistryClient
|
||||
from cleveractors.registry.exceptions import (
|
||||
AccessDeniedError,
|
||||
AuthenticationRequiredError,
|
||||
ConflictError,
|
||||
InternalServerError,
|
||||
InvalidPackageIdError,
|
||||
PackageNotFoundError,
|
||||
RegistryError,
|
||||
RegistryNetworkError,
|
||||
ValidationError,
|
||||
VersionNotFoundError,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"RegistryClient",
|
||||
"RegistryError",
|
||||
"PackageNotFoundError",
|
||||
"InvalidPackageIdError",
|
||||
"VersionNotFoundError",
|
||||
"ValidationError",
|
||||
"AuthenticationRequiredError",
|
||||
"AccessDeniedError",
|
||||
"ConflictError",
|
||||
"InternalServerError",
|
||||
"RegistryNetworkError",
|
||||
]
|
||||
@@ -0,0 +1,217 @@
|
||||
"""Async HTTP client for the Package Registry Standard v1.0.0.
|
||||
|
||||
Implements all 4 defined HTTP API endpoints (§8) using httpx.AsyncClient:
|
||||
- GET /packages/{id} (§8.2.1)
|
||||
- GET /{type}/{ns}/{name} (§8.2.2)
|
||||
- GET /browse (§8.4.1)
|
||||
- GET /.well-known/cleverthis-packages (§8.4.2)
|
||||
|
||||
Supports version alias resolution per §4.2, anonymous reads (§9.1),
|
||||
and optional API key authentication (§9.2).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from cleveractors.registry.exceptions import (
|
||||
_ERROR_TYPE_MAP,
|
||||
RegistryNetworkError,
|
||||
exception_for_status,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_PACKAGE_TYPE_PREFIXES: dict[str, str] = {
|
||||
"actor": "pkg_act_",
|
||||
"graph": "pkg_grh_",
|
||||
"stream": "pkg_str_",
|
||||
"agent": "pkg_agt_",
|
||||
"template": "pkg_tpl_",
|
||||
"skill": "pkg_skl_",
|
||||
"mcp": "pkg_mcp_",
|
||||
"lsp": "pkg_lsp_",
|
||||
|
brent.edwards marked this conversation as resolved
Outdated
brent.edwards
commented
When I look at https://git.cleverthis.com/cleveragents/cleveractors-core/pulls/31/files , file src/cleveractors/registry/types.py , lines 14-21 and I see this file, I get nervous. This is close to the same information in two different files. What happens when a new package type appears in one but not the other? I strongly recommend that the prefixes be in the same file, next to each other, so that it will be harder for them to get out of sync. When I look at https://git.cleverthis.com/cleveragents/cleveractors-core/pulls/31/files , file src/cleveractors/registry/types.py , lines 14-21 and I see this file, I get nervous. This is close to the same information in two different files.
What happens when a new package type appears in one but not the other?
I strongly recommend that the prefixes be in the same file, next to each other, so that it will be harder for them to get out of sync.
CoreRasurae
commented
That will be consolidated better after merging this PR and with other PR merged too. For now these are independent works. That will be consolidated better after merging this PR and with other PR merged too. For now these are independent works.
|
||||
}
|
||||
|
||||
|
||||
class RegistryClient:
|
||||
"""Async HTTP client for the Package Registry Standard v1.0.0.
|
||||
|
||||
Configured per server URL. Uses ``httpx.AsyncClient`` internally
|
||||
for all HTTP operations.
|
||||
|
||||
Attributes:
|
||||
base_url: The registry server base URL.
|
||||
api_key: Optional API key for authenticated operations.
|
||||
timeout: Request timeout in seconds.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
api_key: Optional[str] = None,
|
||||
timeout: float = 30.0,
|
||||
) -> None:
|
||||
if not base_url:
|
||||
raise ValueError("base_url must not be empty")
|
||||
self.base_url: str = base_url.rstrip("/")
|
||||
self.api_key: Optional[str] = api_key
|
||||
self.timeout: float = timeout
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
async def _get_client(self) -> httpx.AsyncClient:
|
||||
if self._client is None or self._client.is_closed:
|
||||
headers: dict[str, str] = {}
|
||||
if self.api_key:
|
||||
headers["Authorization"] = f"Bearer {self.api_key}"
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=self.base_url,
|
||||
headers=headers,
|
||||
timeout=httpx.Timeout(self.timeout),
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def _request(self, path: str, params: Optional[dict[str, str]] = None) -> Any:
|
||||
client = await self._get_client()
|
||||
try:
|
||||
response = await client.get(path, params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as exc:
|
||||
status = exc.response.status_code
|
||||
msg = f"HTTP {status}"
|
||||
error_type: Optional[str] = None
|
||||
try:
|
||||
body = exc.response.json()
|
||||
if isinstance(body, dict):
|
||||
error_block = body.get("error")
|
||||
if isinstance(error_block, dict):
|
||||
error_type = error_block.get("type")
|
||||
if "message" in error_block:
|
||||
msg = error_block["message"]
|
||||
elif "message" in body:
|
||||
msg = body["message"]
|
||||
except (ValueError, KeyError, TypeError):
|
||||
pass
|
||||
if error_type and error_type in _ERROR_TYPE_MAP:
|
||||
raise _ERROR_TYPE_MAP[error_type](msg) from exc
|
||||
raise exception_for_status(status, msg) from exc
|
||||
except httpx.RequestError as exc:
|
||||
raise RegistryNetworkError(str(exc)) from exc
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close the underlying HTTP client."""
|
||||
if self._client is not None and not self._client.is_closed:
|
||||
await self._client.aclose()
|
||||
self._client = None
|
||||
|
||||
async def __aenter__(self) -> "RegistryClient":
|
||||
await self._get_client()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args: Any) -> None:
|
||||
await self.close()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# §8.2.1 GET /packages/{package_id}
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_package(self, package_id: str) -> dict[str, Any]:
|
||||
"""Retrieve raw package content by Package ID.
|
||||
|
||||
Args:
|
||||
package_id: The globally unique Package ID.
|
||||
|
||||
Returns:
|
||||
The package content as a dictionary.
|
||||
|
||||
Raises:
|
||||
PackageNotFoundError: If the package does not exist.
|
||||
InvalidPackageIdError: If the Package ID format is invalid.
|
||||
RegistryNetworkError: For connection errors and timeouts.
|
||||
"""
|
||||
return await self._request(f"/packages/{package_id}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# §8.2.2 GET /{package_type}/{namespace}/{name}?version={version}
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def resolve_package(
|
||||
self,
|
||||
package_type: str,
|
||||
namespace: str,
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Resolve a package reference to its concrete Package ID.
|
||||
|
||||
Performs version alias resolution according to §4.2:
|
||||
- Concrete versions (vX.Y.Z) resolve directly.
|
||||
- Mutable aliases (latest, vx, vX.Y.x, vX.x) resolve to
|
||||
the newest matching concrete version.
|
||||
|
||||
Args:
|
||||
package_type: One of the defined package types (§3.2).
|
||||
namespace: The namespace owning the package.
|
||||
name: The package name within its namespace.
|
||||
version: Optional version string or alias. Defaults to ``latest``.
|
||||
|
||||
Returns:
|
||||
A dict with ``package_id`` and ``type`` keys.
|
||||
|
||||
Raises:
|
||||
PackageNotFoundError: If the package does not exist.
|
||||
VersionNotFoundError: If the version/alias cannot be resolved.
|
||||
RegistryNetworkError: For connection errors and timeouts.
|
||||
"""
|
||||
params: dict[str, str] = {}
|
||||
if version:
|
||||
params["version"] = version
|
||||
return await self._request(f"/{package_type}/{namespace}/{name}", params=params)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# §8.4.1 GET /browse
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def browse(
|
||||
self,
|
||||
package_type: Optional[str] = None,
|
||||
namespace: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Browse published packages with optional filters.
|
||||
|
||||
Args:
|
||||
package_type: Filter by package type (§3.2).
|
||||
namespace: Filter by namespace.
|
||||
|
||||
Returns:
|
||||
A dict with ``packages`` (list) and ``count`` keys.
|
||||
|
||||
Raises:
|
||||
RegistryNetworkError: For connection errors and timeouts.
|
||||
"""
|
||||
params: dict[str, str] = {}
|
||||
if package_type:
|
||||
params["type"] = package_type
|
||||
if namespace:
|
||||
params["namespace"] = namespace
|
||||
return await self._request("/browse", params=params)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# §8.4.2 GET /.well-known/cleverthis-packages
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def discover(self) -> dict[str, Any]:
|
||||
"""Retrieve registry metadata via the well-known discovery endpoint.
|
||||
|
||||
Returns:
|
||||
A dict with registry metadata: name, version, supported_types,
|
||||
authentication methods, and features.
|
||||
|
||||
Raises:
|
||||
RegistryNetworkError: For connection errors and timeouts.
|
||||
"""
|
||||
return await self._request("/.well-known/cleverthis-packages")
|
||||
@@ -0,0 +1,76 @@
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""Typed exceptions for the Package Registry client.
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
Maps the 8 error types defined in the Package Registry Standard §13.2
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
to Python exception classes, plus a network error type for transport failures.
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
from cleveractors.core.exceptions import CleverAgentsException
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class RegistryError(CleverAgentsException):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""Base exception for all registry-related errors."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class PackageNotFoundError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""The requested package does not exist (HTTP 404)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class InvalidPackageIdError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""The provided Package ID format is invalid (HTTP 400)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class VersionNotFoundError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""The requested version does not exist (HTTP 404)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class ValidationError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""The package failed validation (HTTP 400)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class AuthenticationRequiredError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""Authentication is required for this operation (HTTP 401)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class AccessDeniedError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""Access to the requested resource is denied (HTTP 403)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class ConflictError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""The request conflicts with existing data (HTTP 409)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class InternalServerError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""An unexpected error occurred server-side (HTTP 500)."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
class RegistryNetworkError(RegistryError):
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""Connection errors, timeouts, or other transport-level failures."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
_ERROR_TYPE_MAP: dict[str, type[RegistryError]] = {
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"PackageNotFound": PackageNotFoundError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"InvalidPackageId": InvalidPackageIdError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"VersionNotFound": VersionNotFoundError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"ValidationError": ValidationError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"AuthenticationRequired": AuthenticationRequiredError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"AccessDenied": AccessDeniedError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"Conflict": ConflictError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"InternalServerError": InternalServerError,
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
}
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
def exception_for_status(status_code: int, message: str) -> RegistryError:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
"""Return an appropriate RegistryError for an HTTP status code."""
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
if status_code == 400:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return InvalidPackageIdError(message)
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
if status_code == 401:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return AuthenticationRequiredError(message)
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
if status_code == 403:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return AccessDeniedError(message)
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
if status_code == 404:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return PackageNotFoundError(message)
|
||||
|
brent.edwards marked this conversation as resolved
Outdated
brent.edwards
commented
The above list of errors maps from The above list of errors maps from `PackageNotFoundError` and `VersionNotFoundError` to 404. How do you know that this is a `PackageNotFoundError` instead of a `VersionNotFoundError`?
CoreRasurae
commented
Fixed. The _request() method now parses the standard error format {"error": {"type": "...", "message": "..."}} from the JSON response body per Package Registry Standard §13.1. When the error type is present, it is matched against the defined types in _ERROR_TYPE_MAP to raise the correct specific exception. The status-code fallback via exception_for_status() is preserved for servers that do not include the type field or return non-standard error formats. Fixed. The _request() method now parses the standard error format {"error": {"type": "...", "message": "..."}} from the JSON response body per Package Registry Standard §13.1. When the error type is present, it is matched against the defined types in _ERROR_TYPE_MAP to raise the correct specific exception. The status-code fallback via exception_for_status() is preserved for servers that do not include the type field or return non-standard error formats.
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
if status_code == 409:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return ConflictError(message)
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
if 500 <= status_code < 600:
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return InternalServerError(message)
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
return RegistryError(message)
|
||||
|
brent.edwards marked this conversation as resolved
CoreRasurae
commented
How do you know this isn't a How do you know this isn't a `ValidationError`?
CoreRasurae
commented
(Replying to your inline comment on The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body. Per Package Registry Standard §13.1, compliant servers return errors as: Extract Test added: (Replying to your inline comment on `src/cleveractors/registry/exceptions.py` in the `exception_for_status` function)
The exception_for_status() function is the fallback — it only activates when the server does not include an explicit error type in the response body.
Per Package Registry Standard §13.1, compliant servers return errors as:
```
{"error": {"type": "ValidationError", "message": "..."}}
The _request() method now parses this structure first:
```
Extract `body["error"]["type"]` from the JSON response
Look it up in `_ERROR_TYPE_MAP` to get the correct exception class
Raise that specific exception (e.g.` ValidationError, VersionNotFoundError, ...`)
Only when the body lacks a recognizable error.type field does `exception_for_status()` act as the status-code-only fallback
This means a standards-compliant server can signal either ValidationError or InvalidPackageIdError at 400 by including the appropriate type value, and the client will raise the correct exception.
Test added:
```
Scenario: 400 with ValidationError error type in body raises ValidationError
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "..."}}'
Then a ValidationError should be raised
```
|
||||
The 418 return code means: "I'm a teapot". Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/418
Are you sure that's the return code that you want to use?