From 1b2a4ecd06329760d0dceddbf1f880016a58abbc Mon Sep 17 00:00:00 2001 From: njbrake <33383515+njbrake@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:11:35 +0000 Subject: [PATCH] chore: regenerate SDK client core from Otari OpenAPI spec --- sdk-endpoints.txt | 1 + src/otari/_client/__init__.py | 4 + src/otari/_client/api/routing_api.py | 6 +- src/otari/_client/api/usage_api.py | 248 ++++++++++++++++++ src/otari/_client/models/__init__.py | 2 + src/otari/_client/models/explain_response.py | 8 +- src/otari/_client/models/in_flight_entry.py | 125 +++++++++ .../_client/models/in_flight_response.py | 98 +++++++ src/otari/_client/models/learned_policy.py | 2 +- 9 files changed, 487 insertions(+), 7 deletions(-) create mode 100644 src/otari/_client/models/in_flight_entry.py create mode 100644 src/otari/_client/models/in_flight_response.py diff --git a/sdk-endpoints.txt b/sdk-endpoints.txt index d1617a2..02917ef 100644 --- a/sdk-endpoints.txt +++ b/sdk-endpoints.txt @@ -119,6 +119,7 @@ GET /v1/usage/series # not yet wrapped DELETE /v1/usage # not yet wrapped POST /v1/usage/external-events # not yet wrapped POST /v1/usage/set-price # not yet wrapped +GET /v1/usage/in-flight # dashboard-only, per-worker live view # Routing policies GET /v1/routing/policies # not yet wrapped POST /v1/routing/policies # not yet wrapped diff --git a/src/otari/_client/__init__.py b/src/otari/_client/__init__.py index e417bea..0f51d5d 100644 --- a/src/otari/_client/__init__.py +++ b/src/otari/_client/__init__.py @@ -148,6 +148,8 @@ "IMGUsageOutputTokensDetails", "ImageGenerationRequest", "ImagesResponse", + "InFlightEntry", + "InFlightResponse", "Input", "Input1", "KeyInfo", @@ -505,6 +507,8 @@ from otari._client.models.img_usage_output_tokens_details import IMGUsageOutputTokensDetails as IMGUsageOutputTokensDetails from otari._client.models.image_generation_request import ImageGenerationRequest as ImageGenerationRequest from otari._client.models.images_response import ImagesResponse as ImagesResponse +from otari._client.models.in_flight_entry import InFlightEntry as InFlightEntry +from otari._client.models.in_flight_response import InFlightResponse as InFlightResponse from otari._client.models.input import Input as Input from otari._client.models.input1 import Input1 as Input1 from otari._client.models.key_info import KeyInfo as KeyInfo diff --git a/src/otari/_client/api/routing_api.py b/src/otari/_client/api/routing_api.py index 3761f9a..0f07130 100644 --- a/src/otari/_client/api/routing_api.py +++ b/src/otari/_client/api/routing_api.py @@ -870,7 +870,7 @@ def rank_candidates_v1_routing_preferences_rank_post( ) -> RankResponse: """Rank Candidates - Record scored examples: one routing-memory record each, plus an audit row. The routing-memory record is written before its audit row for each example, because it is the load-bearing one (the router votes over it) and embedding it can fail; writing the audit row only afterwards means a failed embedding never leaves an orphan audit row. A failed embedding is a 502 that names the model, not a 500. Every example in the batch is embedded, so this is the call an operator makes most often and the one most likely to meet a misconfigured ``router_embedding_model``. Score keys are stored in the spelling the policy uses (see :func:`_validated_scores`), because the router matches them against its candidate selectors by exact string. + Record scored examples: one routing-memory record each, plus an audit row. The routing-memory record is written before its audit row for each example, because it is the load-bearing one (the router votes over it) and embedding it can fail; writing the audit row only afterwards means a failed embedding never leaves an orphan audit row. A failed embedding is a 502 that names the model, not a 500. Every example in the batch is embedded, so this is the call an operator makes most often and the one most likely to meet a misconfigured ``router_embedding_model``. Score keys are stored canonically as ``instance:model`` (see :func:`_validated_scores`), which is the form the router canonicalizes its candidates to, so how a policy spells a candidate cannot decide whether it matches. :param rank_request: (required) :type rank_request: RankRequest @@ -938,7 +938,7 @@ def rank_candidates_v1_routing_preferences_rank_post_with_http_info( ) -> ApiResponse[RankResponse]: """Rank Candidates - Record scored examples: one routing-memory record each, plus an audit row. The routing-memory record is written before its audit row for each example, because it is the load-bearing one (the router votes over it) and embedding it can fail; writing the audit row only afterwards means a failed embedding never leaves an orphan audit row. A failed embedding is a 502 that names the model, not a 500. Every example in the batch is embedded, so this is the call an operator makes most often and the one most likely to meet a misconfigured ``router_embedding_model``. Score keys are stored in the spelling the policy uses (see :func:`_validated_scores`), because the router matches them against its candidate selectors by exact string. + Record scored examples: one routing-memory record each, plus an audit row. The routing-memory record is written before its audit row for each example, because it is the load-bearing one (the router votes over it) and embedding it can fail; writing the audit row only afterwards means a failed embedding never leaves an orphan audit row. A failed embedding is a 502 that names the model, not a 500. Every example in the batch is embedded, so this is the call an operator makes most often and the one most likely to meet a misconfigured ``router_embedding_model``. Score keys are stored canonically as ``instance:model`` (see :func:`_validated_scores`), which is the form the router canonicalizes its candidates to, so how a policy spells a candidate cannot decide whether it matches. :param rank_request: (required) :type rank_request: RankRequest @@ -1006,7 +1006,7 @@ def rank_candidates_v1_routing_preferences_rank_post_without_preload_content( ) -> RESTResponseType: """Rank Candidates - Record scored examples: one routing-memory record each, plus an audit row. The routing-memory record is written before its audit row for each example, because it is the load-bearing one (the router votes over it) and embedding it can fail; writing the audit row only afterwards means a failed embedding never leaves an orphan audit row. A failed embedding is a 502 that names the model, not a 500. Every example in the batch is embedded, so this is the call an operator makes most often and the one most likely to meet a misconfigured ``router_embedding_model``. Score keys are stored in the spelling the policy uses (see :func:`_validated_scores`), because the router matches them against its candidate selectors by exact string. + Record scored examples: one routing-memory record each, plus an audit row. The routing-memory record is written before its audit row for each example, because it is the load-bearing one (the router votes over it) and embedding it can fail; writing the audit row only afterwards means a failed embedding never leaves an orphan audit row. A failed embedding is a 502 that names the model, not a 500. Every example in the batch is embedded, so this is the call an operator makes most often and the one most likely to meet a misconfigured ``router_embedding_model``. Score keys are stored canonically as ``instance:model`` (see :func:`_validated_scores`), which is the form the router canonicalizes its candidates to, so how a policy spells a candidate cannot decide whether it matches. :param rank_request: (required) :type rank_request: RankRequest diff --git a/src/otari/_client/api/usage_api.py b/src/otari/_client/api/usage_api.py index 85d20c6..13baf3b 100644 --- a/src/otari/_client/api/usage_api.py +++ b/src/otari/_client/api/usage_api.py @@ -21,6 +21,7 @@ from typing_extensions import Annotated from otari._client.models.external_events_request import ExternalEventsRequest from otari._client.models.external_ingest_result import ExternalIngestResult +from otari._client.models.in_flight_response import InFlightResponse from otari._client.models.usage_count import UsageCount from otari._client.models.usage_delete_request import UsageDeleteRequest from otari._client.models.usage_delete_result import UsageDeleteResult @@ -1131,6 +1132,253 @@ def _ingest_external_usage_v1_usage_external_events_post_serialize( + @validate_call + def list_in_flight_v1_usage_in_flight_get( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> InFlightResponse: + """List In Flight + + Requests the gateway is currently serving, longest-running first. A usage row is written when a request settles, so the log alone cannot answer \"is anything happening right now\": on a slow backend, a 30-second local model call is invisible until it finishes. This reports what is in progress. Read from an in-memory registry, so it describes the process that answers this call and not the deployment: behind a load balancer, consecutive polls reach different otari processes, and there is no deployment-wide total to ask for. ``total`` is the true in-flight count for the answering process even when ``requests`` is capped. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_in_flight_v1_usage_in_flight_get_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "InFlightResponse", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def list_in_flight_v1_usage_in_flight_get_with_http_info( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[InFlightResponse]: + """List In Flight + + Requests the gateway is currently serving, longest-running first. A usage row is written when a request settles, so the log alone cannot answer \"is anything happening right now\": on a slow backend, a 30-second local model call is invisible until it finishes. This reports what is in progress. Read from an in-memory registry, so it describes the process that answers this call and not the deployment: behind a load balancer, consecutive polls reach different otari processes, and there is no deployment-wide total to ask for. ``total`` is the true in-flight count for the answering process even when ``requests`` is capped. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_in_flight_v1_usage_in_flight_get_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "InFlightResponse", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def list_in_flight_v1_usage_in_flight_get_without_preload_content( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List In Flight + + Requests the gateway is currently serving, longest-running first. A usage row is written when a request settles, so the log alone cannot answer \"is anything happening right now\": on a slow backend, a 30-second local model call is invisible until it finishes. This reports what is in progress. Read from an in-memory registry, so it describes the process that answers this call and not the deployment: behind a load balancer, consecutive polls reach different otari processes, and there is no deployment-wide total to ask for. ``total`` is the true in-flight count for the answering process even when ``requests`` is capped. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_in_flight_v1_usage_in_flight_get_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "InFlightResponse", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_in_flight_v1_usage_in_flight_get_serialize( + self, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'XApiKeyAuth', + 'ApiKeyAuth' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/v1/usage/in-flight', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + @validate_call def list_usage_v1_usage_get( self, diff --git a/src/otari/_client/models/__init__.py b/src/otari/_client/models/__init__.py index ab88bc8..27e4e29 100644 --- a/src/otari/_client/models/__init__.py +++ b/src/otari/_client/models/__init__.py @@ -108,6 +108,8 @@ from otari._client.models.img_usage_output_tokens_details import IMGUsageOutputTokensDetails from otari._client.models.image_generation_request import ImageGenerationRequest from otari._client.models.images_response import ImagesResponse +from otari._client.models.in_flight_entry import InFlightEntry +from otari._client.models.in_flight_response import InFlightResponse from otari._client.models.input import Input from otari._client.models.input1 import Input1 from otari._client.models.key_info import KeyInfo diff --git a/src/otari/_client/models/explain_response.py b/src/otari/_client/models/explain_response.py index b551d80..2c6e820 100644 --- a/src/otari/_client/models/explain_response.py +++ b/src/otari/_client/models/explain_response.py @@ -17,8 +17,8 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, StrictBool, StrictStr -from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional, Union from otari._client.models.candidate_response import CandidateResponse from otari._client.models.dropped_response import DroppedResponse from typing import Optional, Set @@ -36,8 +36,9 @@ class ExplainResponse(BaseModel): name: StrictStr router_backend: Optional[StrictStr] = None router_candidates: Optional[List[StrictStr]] = None + router_weights: Optional[Dict[str, Union[StrictFloat, StrictInt]]] = Field(default=None, description="For a weighted policy, the percentage of traffic each candidate receives, normalized over the candidates this caller may use. Empty for every other policy, and for a weighted policy whose whole split this caller may not use: a split over no candidate is not a split, and each filtered candidate is named in `dropped` instead. A weighted split needs no request state, so unlike a learned router's ranking it is knowable here: the plan above is the real ordering by share, not the decline path.") selection_reason: StrictStr - __properties: ClassVar[List[str]] = ["candidates", "dropped", "guardrails", "is_dynamic", "name", "router_backend", "router_candidates", "selection_reason"] + __properties: ClassVar[List[str]] = ["candidates", "dropped", "guardrails", "is_dynamic", "name", "router_backend", "router_candidates", "router_weights", "selection_reason"] model_config = ConfigDict( validate_by_name=True, @@ -116,6 +117,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "name": obj.get("name"), "router_backend": obj.get("router_backend"), "router_candidates": obj.get("router_candidates"), + "router_weights": obj.get("router_weights"), "selection_reason": obj.get("selection_reason") }) return _obj diff --git a/src/otari/_client/models/in_flight_entry.py b/src/otari/_client/models/in_flight_entry.py new file mode 100644 index 0000000..f0773e2 --- /dev/null +++ b/src/otari/_client/models/in_flight_entry.py @@ -0,0 +1,125 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from datetime import datetime +from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class InFlightEntry(BaseModel): + """ + One request the gateway is serving right now. Field names match their ``UsageEntry`` counterparts so a request reads the same way in flight as it does once it has settled. ``id`` is the exception: it is an ephemeral tracking id, not the id of the usage row this will become. + """ # noqa: E501 + api_key_id: Optional[StrictStr] + elapsed_ms: StrictInt + endpoint: StrictStr + id: StrictStr + model: StrictStr + policy_name: Optional[StrictStr] + provider: Optional[StrictStr] + started_at: datetime + user_id: Optional[StrictStr] + __properties: ClassVar[List[str]] = ["api_key_id", "elapsed_ms", "endpoint", "id", "model", "policy_name", "provider", "started_at", "user_id"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of InFlightEntry from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # set to None if api_key_id (nullable) is None + # and model_fields_set contains the field + if self.api_key_id is None and "api_key_id" in self.model_fields_set: + _dict['api_key_id'] = None + + # set to None if policy_name (nullable) is None + # and model_fields_set contains the field + if self.policy_name is None and "policy_name" in self.model_fields_set: + _dict['policy_name'] = None + + # set to None if provider (nullable) is None + # and model_fields_set contains the field + if self.provider is None and "provider" in self.model_fields_set: + _dict['provider'] = None + + # set to None if user_id (nullable) is None + # and model_fields_set contains the field + if self.user_id is None and "user_id" in self.model_fields_set: + _dict['user_id'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of InFlightEntry from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "api_key_id": obj.get("api_key_id"), + "elapsed_ms": obj.get("elapsed_ms"), + "endpoint": obj.get("endpoint"), + "id": obj.get("id"), + "model": obj.get("model"), + "policy_name": obj.get("policy_name"), + "provider": obj.get("provider"), + "started_at": obj.get("started_at"), + "user_id": obj.get("user_id") + }) + return _obj + + diff --git a/src/otari/_client/models/in_flight_response.py b/src/otari/_client/models/in_flight_response.py new file mode 100644 index 0000000..357d2b6 --- /dev/null +++ b/src/otari/_client/models/in_flight_response.py @@ -0,0 +1,98 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictInt +from typing import Any, ClassVar, Dict, List +from otari._client.models.in_flight_entry import InFlightEntry +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class InFlightResponse(BaseModel): + """ + The requests in flight on the answering worker. + """ # noqa: E501 + requests: List[InFlightEntry] + total: StrictInt + __properties: ClassVar[List[str]] = ["requests", "total"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of InFlightResponse from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in requests (list) + _items = [] + if self.requests: + for _item_requests in self.requests: + if _item_requests: + _items.append(_item_requests.to_dict()) + _dict['requests'] = _items + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of InFlightResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "requests": [InFlightEntry.from_dict(_item) for _item in obj["requests"]] if obj.get("requests") is not None else None, + "total": obj.get("total") + }) + return _obj + + diff --git a/src/otari/_client/models/learned_policy.py b/src/otari/_client/models/learned_policy.py index 120748a..325b2ee 100644 --- a/src/otari/_client/models/learned_policy.py +++ b/src/otari/_client/models/learned_policy.py @@ -25,7 +25,7 @@ class LearnedPolicy(BaseModel): """ - A policy whose selection depends on the router, for the status overview. + A policy whose selection depends on routing memory, for the status overview. Only a *learned* policy belongs here. A weighted policy names a router too, but its split is written in the policy document, so listing it under a warmth report would tie it to a pool it never reads. """ # noqa: E501 backend: StrictStr candidates: List[StrictStr]