Skip to content

Commit 1664bff

Browse files
njbrakeclaude
andcommitted
Wire generated control-plane client into OtariClient
- Regenerate the control-plane client as a clean subpackage (otari._control_plane). - Add OtariClient.control_plane: typed accessors (keys/users/budgets/pricing/usage) sharing one client authed with Authorization: Bearer using an admin credential (admin_key / GATEWAY_ADMIN_KEY / platform_token). Management endpoints require Bearer, not the Otari-Key inference header. - Add admin_key param + ControlPlane facade; export ControlPlane. - Exclude the generated subpackage from ruff/mypy; add its runtime deps. - Integration tests now drive the public control_plane surface end-to-end (full CRUD per endpoint + admin-credential guard); 6 pass against a live gateway on SQLite. Part of mozilla-ai/otari#96 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ee86b3b commit 1664bff

100 files changed

Lines changed: 324 additions & 4391 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ classifiers = [
2424
dependencies = [
2525
"openai>=1.99.3",
2626
"httpx>=0.25.0",
27+
# Runtime deps of the generated control-plane client (otari._control_plane).
28+
"urllib3>=2.1.0",
29+
"python-dateutil>=2.8.2",
30+
"pydantic>=2.0.0",
2731
]
2832

2933
[project.urls]
@@ -50,6 +54,8 @@ asyncio_mode = "auto"
5054
[tool.ruff]
5155
target-version = "py311"
5256
line-length = 120
57+
# Generated control-plane client (OpenAPI Generator output) is not hand-linted.
58+
extend-exclude = ["src/otari/_control_plane"]
5359

5460
[tool.ruff.lint]
5561
select = ["E", "F", "I", "N", "W", "UP", "S", "B", "A", "C4", "DTZ", "T10", "ISC", "ICN", "PIE", "PT", "RSE", "RET", "SIM", "TID", "TCH", "ARG", "PLC", "PLE", "PLW", "TRY", "FLY", "PERF", "RUF"]
@@ -63,3 +69,10 @@ python_version = "3.11"
6369
strict = true
6470
warn_return_any = true
6571
warn_unused_configs = true
72+
# Generated control-plane client (OpenAPI Generator output) is not type-checked here.
73+
exclude = ["src/otari/_control_plane/"]
74+
75+
[[tool.mypy.overrides]]
76+
module = ["otari._control_plane.*"]
77+
ignore_errors = true
78+
ignore_missing_imports = true

src/otari/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from otari.async_client import AsyncOtariClient
2222
from otari.client import OtariClient
23+
from otari.control_plane import ControlPlane
2324
from otari.errors import (
2425
AuthenticationError,
2526
BatchNotCompleteError,
@@ -69,6 +70,7 @@
6970
"ChatCompletion",
7071
"ChatCompletionChunk",
7172
"ChatCompletionMessageParam",
73+
"ControlPlane",
7274
"CreateBatchParams",
7375
"CreateEmbeddingResponse",
7476
"EmbeddingCreateParams",

src/otari/_base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
# Matches the gateway server's own alias chain (OTARI_AI_TOKEN preferred).
4747
_ENV_PLATFORM_TOKEN = "OTARI_AI_TOKEN" # noqa: S105
4848
_ENV_PLATFORM_TOKEN_LEGACY = "GATEWAY_PLATFORM_TOKEN" # noqa: S105
49+
# Admin/master credential for the control-plane (management) endpoints.
50+
_ENV_ADMIN_KEY = "GATEWAY_ADMIN_KEY"
4951

5052
_STATUS_TO_ERROR: dict[int, type[AuthenticationError] | type[ModelNotFoundError]] = {
5153
401: AuthenticationError,
@@ -72,6 +74,7 @@ def __init__(
7274
*,
7375
api_key: str | None = None,
7476
platform_token: str | None = None,
77+
admin_key: str | None = None,
7578
default_headers: dict[str, str] | None = None,
7679
openai_options: dict[str, Any] | None = None,
7780
) -> None:
@@ -149,6 +152,17 @@ def __init__(
149152
if default_headers:
150153
self._auth_headers.update(default_headers)
151154

155+
# Control-plane (management) auth. Those endpoints expect
156+
# ``Authorization: Bearer <admin/master key>``. In platform mode the
157+
# platform token already serves as that bearer; for a self-hosted
158+
# gateway the caller passes the master key as ``admin_key`` (or via
159+
# ``GATEWAY_ADMIN_KEY``). The control-plane client targets the gateway
160+
# root (the generated paths already include the ``/v1`` prefix).
161+
self._gateway_root_url = api_base_url.removesuffix("/v1")
162+
self._admin_token: str | None = (
163+
admin_key or os.environ.get(_ENV_ADMIN_KEY) or resolved_platform_token
164+
)
165+
152166
# -- Error handling -----------------------------------------------------
153167

154168
def _handle_error(self, error: Exception) -> None:
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# coding: utf-8
2+
3+
# flake8: noqa
4+
5+
"""
6+
otari-gateway
7+
8+
A clean FastAPI gateway for otari with API key management
9+
10+
The version of the OpenAPI document: 0.0.0-dev
11+
Generated by OpenAPI Generator (https://openapi-generator.tech)
12+
13+
Do not edit the class manually.
14+
""" # noqa: E501
15+
16+
17+
__version__ = "1.0.0"
18+
19+
# Define package exports
20+
__all__ = [
21+
"BudgetsApi",
22+
"KeysApi",
23+
"PricingApi",
24+
"UsageApi",
25+
"UsersApi",
26+
"ApiResponse",
27+
"ApiClient",
28+
"Configuration",
29+
"OpenApiException",
30+
"ApiTypeError",
31+
"ApiValueError",
32+
"ApiKeyError",
33+
"ApiAttributeError",
34+
"ApiException",
35+
"BudgetResponse",
36+
"CreateBudgetRequest",
37+
"CreateKeyRequest",
38+
"CreateKeyResponse",
39+
"CreateUserRequest",
40+
"HTTPValidationError",
41+
"KeyInfo",
42+
"LocationInner",
43+
"PricingResponse",
44+
"SetPricingRequest",
45+
"UpdateBudgetRequest",
46+
"UpdateKeyRequest",
47+
"UpdateUserRequest",
48+
"UsageEntry",
49+
"UsageLogResponse",
50+
"UserResponse",
51+
"ValidationError",
52+
]
53+
54+
# import apis into sdk package
55+
from otari._control_plane.api.budgets_api import BudgetsApi as BudgetsApi
56+
from otari._control_plane.api.keys_api import KeysApi as KeysApi
57+
from otari._control_plane.api.pricing_api import PricingApi as PricingApi
58+
from otari._control_plane.api.usage_api import UsageApi as UsageApi
59+
from otari._control_plane.api.users_api import UsersApi as UsersApi
60+
61+
# import ApiClient
62+
from otari._control_plane.api_response import ApiResponse as ApiResponse
63+
from otari._control_plane.api_client import ApiClient as ApiClient
64+
from otari._control_plane.configuration import Configuration as Configuration
65+
from otari._control_plane.exceptions import OpenApiException as OpenApiException
66+
from otari._control_plane.exceptions import ApiTypeError as ApiTypeError
67+
from otari._control_plane.exceptions import ApiValueError as ApiValueError
68+
from otari._control_plane.exceptions import ApiKeyError as ApiKeyError
69+
from otari._control_plane.exceptions import ApiAttributeError as ApiAttributeError
70+
from otari._control_plane.exceptions import ApiException as ApiException
71+
72+
# import models into sdk package
73+
from otari._control_plane.models.budget_response import BudgetResponse as BudgetResponse
74+
from otari._control_plane.models.create_budget_request import CreateBudgetRequest as CreateBudgetRequest
75+
from otari._control_plane.models.create_key_request import CreateKeyRequest as CreateKeyRequest
76+
from otari._control_plane.models.create_key_response import CreateKeyResponse as CreateKeyResponse
77+
from otari._control_plane.models.create_user_request import CreateUserRequest as CreateUserRequest
78+
from otari._control_plane.models.http_validation_error import HTTPValidationError as HTTPValidationError
79+
from otari._control_plane.models.key_info import KeyInfo as KeyInfo
80+
from otari._control_plane.models.location_inner import LocationInner as LocationInner
81+
from otari._control_plane.models.pricing_response import PricingResponse as PricingResponse
82+
from otari._control_plane.models.set_pricing_request import SetPricingRequest as SetPricingRequest
83+
from otari._control_plane.models.update_budget_request import UpdateBudgetRequest as UpdateBudgetRequest
84+
from otari._control_plane.models.update_key_request import UpdateKeyRequest as UpdateKeyRequest
85+
from otari._control_plane.models.update_user_request import UpdateUserRequest as UpdateUserRequest
86+
from otari._control_plane.models.usage_entry import UsageEntry as UsageEntry
87+
from otari._control_plane.models.usage_log_response import UsageLogResponse as UsageLogResponse
88+
from otari._control_plane.models.user_response import UserResponse as UserResponse
89+
from otari._control_plane.models.validation_error import ValidationError as ValidationError
90+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# flake8: noqa
2+
3+
# import apis into api package
4+
from otari._control_plane.api.budgets_api import BudgetsApi
5+
from otari._control_plane.api.keys_api import KeysApi
6+
from otari._control_plane.api.pricing_api import PricingApi
7+
from otari._control_plane.api.usage_api import UsageApi
8+
from otari._control_plane.api.users_api import UsersApi
9+

src/otari/_generated/otari_control_plane/api/budgets_api.py renamed to src/otari/_control_plane/api/budgets_api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
from pydantic import Field, StrictStr
1919
from typing import List, Optional
2020
from typing_extensions import Annotated
21-
from otari_control_plane.models.budget_response import BudgetResponse
22-
from otari_control_plane.models.create_budget_request import CreateBudgetRequest
23-
from otari_control_plane.models.update_budget_request import UpdateBudgetRequest
21+
from otari._control_plane.models.budget_response import BudgetResponse
22+
from otari._control_plane.models.create_budget_request import CreateBudgetRequest
23+
from otari._control_plane.models.update_budget_request import UpdateBudgetRequest
2424

25-
from otari_control_plane.api_client import ApiClient, RequestSerialized
26-
from otari_control_plane.api_response import ApiResponse
27-
from otari_control_plane.rest import RESTResponseType
25+
from otari._control_plane.api_client import ApiClient, RequestSerialized
26+
from otari._control_plane.api_response import ApiResponse
27+
from otari._control_plane.rest import RESTResponseType
2828

2929

3030
class BudgetsApi:

src/otari/_generated/otari_control_plane/api/keys_api.py renamed to src/otari/_control_plane/api/keys_api.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
from pydantic import Field, StrictStr
1919
from typing import List, Optional
2020
from typing_extensions import Annotated
21-
from otari_control_plane.models.create_key_request import CreateKeyRequest
22-
from otari_control_plane.models.create_key_response import CreateKeyResponse
23-
from otari_control_plane.models.key_info import KeyInfo
24-
from otari_control_plane.models.update_key_request import UpdateKeyRequest
25-
26-
from otari_control_plane.api_client import ApiClient, RequestSerialized
27-
from otari_control_plane.api_response import ApiResponse
28-
from otari_control_plane.rest import RESTResponseType
21+
from otari._control_plane.models.create_key_request import CreateKeyRequest
22+
from otari._control_plane.models.create_key_response import CreateKeyResponse
23+
from otari._control_plane.models.key_info import KeyInfo
24+
from otari._control_plane.models.update_key_request import UpdateKeyRequest
25+
26+
from otari._control_plane.api_client import ApiClient, RequestSerialized
27+
from otari._control_plane.api_response import ApiResponse
28+
from otari._control_plane.rest import RESTResponseType
2929

3030

3131
class KeysApi:

src/otari/_generated/otari_control_plane/api/pricing_api.py renamed to src/otari/_control_plane/api/pricing_api.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
from pydantic import Field, StrictStr
2020
from typing import List, Optional
2121
from typing_extensions import Annotated
22-
from otari_control_plane.models.pricing_response import PricingResponse
23-
from otari_control_plane.models.set_pricing_request import SetPricingRequest
22+
from otari._control_plane.models.pricing_response import PricingResponse
23+
from otari._control_plane.models.set_pricing_request import SetPricingRequest
2424

25-
from otari_control_plane.api_client import ApiClient, RequestSerialized
26-
from otari_control_plane.api_response import ApiResponse
27-
from otari_control_plane.rest import RESTResponseType
25+
from otari._control_plane.api_client import ApiClient, RequestSerialized
26+
from otari._control_plane.api_response import ApiResponse
27+
from otari._control_plane.rest import RESTResponseType
2828

2929

3030
class PricingApi:

src/otari/_generated/otari_control_plane/api/usage_api.py renamed to src/otari/_control_plane/api/usage_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
from pydantic import Field, StrictStr
2020
from typing import List, Optional
2121
from typing_extensions import Annotated
22-
from otari_control_plane.models.usage_entry import UsageEntry
22+
from otari._control_plane.models.usage_entry import UsageEntry
2323

24-
from otari_control_plane.api_client import ApiClient, RequestSerialized
25-
from otari_control_plane.api_response import ApiResponse
26-
from otari_control_plane.rest import RESTResponseType
24+
from otari._control_plane.api_client import ApiClient, RequestSerialized
25+
from otari._control_plane.api_response import ApiResponse
26+
from otari._control_plane.rest import RESTResponseType
2727

2828

2929
class UsageApi:

src/otari/_generated/otari_control_plane/api/users_api.py renamed to src/otari/_control_plane/api/users_api.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
from pydantic import Field, StrictStr
1919
from typing import List, Optional
2020
from typing_extensions import Annotated
21-
from otari_control_plane.models.create_user_request import CreateUserRequest
22-
from otari_control_plane.models.update_user_request import UpdateUserRequest
23-
from otari_control_plane.models.usage_log_response import UsageLogResponse
24-
from otari_control_plane.models.user_response import UserResponse
25-
26-
from otari_control_plane.api_client import ApiClient, RequestSerialized
27-
from otari_control_plane.api_response import ApiResponse
28-
from otari_control_plane.rest import RESTResponseType
21+
from otari._control_plane.models.create_user_request import CreateUserRequest
22+
from otari._control_plane.models.update_user_request import UpdateUserRequest
23+
from otari._control_plane.models.usage_log_response import UsageLogResponse
24+
from otari._control_plane.models.user_response import UserResponse
25+
26+
from otari._control_plane.api_client import ApiClient, RequestSerialized
27+
from otari._control_plane.api_response import ApiResponse
28+
from otari._control_plane.rest import RESTResponseType
2929

3030

3131
class UsersApi:

0 commit comments

Comments
 (0)