From 33914dc3d67d0fa7dc4809b7ff6b9f58d3f1b74d Mon Sep 17 00:00:00 2001 From: svc-excavator-bot Date: Mon, 29 Jun 2026 14:34:27 +0000 Subject: [PATCH] Excavator: Upgrade API Version --- README.md | 18 + docs-snippets-npm/package.json | 2 +- docs-snippets-npm/src/index.ts | 21 +- docs/v2/Functions/Query.md | 178 ++++++++-- docs/v2/Functions/VersionId.md | 2 +- .../StreamingExecuteEventsQueryRequest.md | 14 + .../models/StreamingExecuteQueryResponse.md | 17 + .../v2/Functions/models/StreamingQueryData.md | 12 + .../Functions/models/StreamingQueryError.md | 16 + docs/v2/Ontologies/CipherTextProperty.md | 5 +- docs/v2/Ontologies/ObjectType.md | 3 + .../Ontologies/TimeSeriesValueBankProperty.md | 8 + docs/v2/Ontologies/models/ObjectTypeV2.md | 1 + docs/v2/SqlQueries/SqlQuery.md | 14 +- .../models/ExecuteOntologySqlQueryRequest.md | 2 + docs/v2/SqlQueries/models/ScenarioRid.md | 11 + foundry_sdk/_core/__init__.py | 7 + foundry_sdk/_core/api_client.py | 94 ++++- foundry_sdk/_core/sse.py | 163 +++++++++ foundry_sdk/_errors/__init__.py | 2 + foundry_sdk/_errors/sse_error.py | 24 ++ foundry_sdk/_version.py | 2 +- foundry_sdk/v1/ontologies/errors.py | 82 +++++ foundry_sdk/v2/cli.py | 180 ++++++++-- foundry_sdk/v2/functions/models.py | 49 +++ foundry_sdk/v2/functions/query.py | 334 ++++++++++++++---- foundry_sdk/v2/functions/version_id.py | 4 +- .../v2/ontologies/cipher_text_property.py | 15 +- foundry_sdk/v2/ontologies/errors.py | 82 +++++ foundry_sdk/v2/ontologies/models.py | 5 + foundry_sdk/v2/ontologies/object_type.py | 6 + .../time_series_value_bank_property.py | 17 + foundry_sdk/v2/sql_queries/models.py | 11 + foundry_sdk/v2/sql_queries/sql_query.py | 16 + pyproject.toml | 1 + tests/server.py | 47 +++ 36 files changed, 1307 insertions(+), 158 deletions(-) create mode 100644 docs/v2/Functions/models/StreamingExecuteEventsQueryRequest.md create mode 100644 docs/v2/Functions/models/StreamingExecuteQueryResponse.md create mode 100644 docs/v2/Functions/models/StreamingQueryData.md create mode 100644 docs/v2/Functions/models/StreamingQueryError.md create mode 100644 docs/v2/SqlQueries/models/ScenarioRid.md create mode 100644 foundry_sdk/_core/sse.py create mode 100644 foundry_sdk/_errors/sse_error.py diff --git a/README.md b/README.md index 033943a90..7275d07a8 100644 --- a/README.md +++ b/README.md @@ -828,6 +828,11 @@ Namespace | Resource | Operation | HTTP request | **Filesystem** | ResourceRole | [**list**](docs/v2/Filesystem/ResourceRole.md#list) | **GET** /v2/filesystem/resources/{resourceRid}/roles | **Filesystem** | ResourceRole | [**remove**](docs/v2/Filesystem/ResourceRole.md#remove) | **POST** /v2/filesystem/resources/{resourceRid}/roles/remove | **Filesystem** | Space | [**list**](docs/v2/Filesystem/Space.md#list) | **GET** /v2/filesystem/spaces | +**Functions** | Query | [**execute**](docs/v2/Functions/Query.md#execute) | **POST** /v2/functions/queries/{queryApiName}/execute | +**Functions** | Query | [**get**](docs/v2/Functions/Query.md#get) | **GET** /v2/functions/queries/{queryApiName} | +**Functions** | Query | [**get_by_rid**](docs/v2/Functions/Query.md#get_by_rid) | **GET** /v2/functions/queries/getByRid | +**Functions** | Query | [**get_by_rid_batch**](docs/v2/Functions/Query.md#get_by_rid_batch) | **POST** /v2/functions/queries/getByRidBatch | +**Functions** | Query | [**streaming_execute**](docs/v2/Functions/Query.md#streaming_execute) | **POST** /v2/functions/queries/{queryApiName}/streamingExecute | **MediaSets** | MediaSet | [**abort**](docs/v2/MediaSets/MediaSet.md#abort) | **POST** /v2/mediasets/{mediaSetRid}/transactions/{transactionId}/abort | **MediaSets** | MediaSet | [**clear**](docs/v2/MediaSets/MediaSet.md#clear) | **DELETE** /v2/mediasets/{mediaSetRid}/items/clearAtPath | **MediaSets** | MediaSet | [**commit**](docs/v2/MediaSets/MediaSet.md#commit) | **POST** /v2/mediasets/{mediaSetRid}/transactions/{transactionId}/commit | @@ -1816,7 +1821,11 @@ Namespace | Name | Import | **Functions** | [RegexConstraint](docs/v2/Functions/models/RegexConstraint.md) | `from foundry_sdk.v2.functions.models import RegexConstraint` | **Functions** | [RidConstraint](docs/v2/Functions/models/RidConstraint.md) | `from foundry_sdk.v2.functions.models import RidConstraint` | **Functions** | [RunningExecution](docs/v2/Functions/models/RunningExecution.md) | `from foundry_sdk.v2.functions.models import RunningExecution` | +**Functions** | [StreamingExecuteEventsQueryRequest](docs/v2/Functions/models/StreamingExecuteEventsQueryRequest.md) | `from foundry_sdk.v2.functions.models import StreamingExecuteEventsQueryRequest` | **Functions** | [StreamingExecuteQueryRequest](docs/v2/Functions/models/StreamingExecuteQueryRequest.md) | `from foundry_sdk.v2.functions.models import StreamingExecuteQueryRequest` | +**Functions** | [StreamingExecuteQueryResponse](docs/v2/Functions/models/StreamingExecuteQueryResponse.md) | `from foundry_sdk.v2.functions.models import StreamingExecuteQueryResponse` | +**Functions** | [StreamingQueryData](docs/v2/Functions/models/StreamingQueryData.md) | `from foundry_sdk.v2.functions.models import StreamingQueryData` | +**Functions** | [StreamingQueryError](docs/v2/Functions/models/StreamingQueryError.md) | `from foundry_sdk.v2.functions.models import StreamingQueryError` | **Functions** | [StructConstraint](docs/v2/Functions/models/StructConstraint.md) | `from foundry_sdk.v2.functions.models import StructConstraint` | **Functions** | [StructFieldApiName](docs/v2/Functions/models/StructFieldApiName.md) | `from foundry_sdk.v2.functions.models import StructFieldApiName` | **Functions** | [StructFieldName](docs/v2/Functions/models/StructFieldName.md) | `from foundry_sdk.v2.functions.models import StructFieldName` | @@ -3051,6 +3060,7 @@ Namespace | Name | Import | **SqlQueries** | [ParameterValue](docs/v2/SqlQueries/models/ParameterValue.md) | `from foundry_sdk.v2.sql_queries.models import ParameterValue` | **SqlQueries** | [QueryStatus](docs/v2/SqlQueries/models/QueryStatus.md) | `from foundry_sdk.v2.sql_queries.models import QueryStatus` | **SqlQueries** | [RunningQueryStatus](docs/v2/SqlQueries/models/RunningQueryStatus.md) | `from foundry_sdk.v2.sql_queries.models import RunningQueryStatus` | +**SqlQueries** | [ScenarioRid](docs/v2/SqlQueries/models/ScenarioRid.md) | `from foundry_sdk.v2.sql_queries.models import ScenarioRid` | **SqlQueries** | [SerializationFormat](docs/v2/SqlQueries/models/SerializationFormat.md) | `from foundry_sdk.v2.sql_queries.models import SerializationFormat` | **SqlQueries** | [SqlQueryId](docs/v2/SqlQueries/models/SqlQueryId.md) | `from foundry_sdk.v2.sql_queries.models import SqlQueryId` | **SqlQueries** | [StructColumnFieldType](docs/v2/SqlQueries/models/StructColumnFieldType.md) | `from foundry_sdk.v2.sql_queries.models import StructColumnFieldType` | @@ -3844,6 +3854,7 @@ Namespace | Name | Import | **Ontologies** | ActionTypeNotFound | `from foundry_sdk.v2.ontologies.errors import ActionTypeNotFound` | **Ontologies** | ActionValidationFailed | `from foundry_sdk.v2.ontologies.errors import ActionValidationFailed` | **Ontologies** | AggregationAccuracyNotSupported | `from foundry_sdk.v2.ontologies.errors import AggregationAccuracyNotSupported` | +**Ontologies** | AggregationDepthExceededLimit | `from foundry_sdk.v2.ontologies.errors import AggregationDepthExceededLimit` | **Ontologies** | AggregationGroupCountExceededLimit | `from foundry_sdk.v2.ontologies.errors import AggregationGroupCountExceededLimit` | **Ontologies** | AggregationMemoryExceededLimit | `from foundry_sdk.v2.ontologies.errors import AggregationMemoryExceededLimit` | **Ontologies** | AggregationMetricNotSupported | `from foundry_sdk.v2.ontologies.errors import AggregationMetricNotSupported` | @@ -3922,9 +3933,12 @@ Namespace | Name | Import | **Ontologies** | MarketplaceSdkObjectMappingNotFound | `from foundry_sdk.v2.ontologies.errors import MarketplaceSdkObjectMappingNotFound` | **Ontologies** | MarketplaceSdkPropertyMappingNotFound | `from foundry_sdk.v2.ontologies.errors import MarketplaceSdkPropertyMappingNotFound` | **Ontologies** | MarketplaceSdkQueryMappingNotFound | `from foundry_sdk.v2.ontologies.errors import MarketplaceSdkQueryMappingNotFound` | +**Ontologies** | MediaUploadDestinationNotConfigured | `from foundry_sdk.v2.ontologies.errors import MediaUploadDestinationNotConfigured` | +**Ontologies** | MediaUploadPropertyNotBackedByMediaSetView | `from foundry_sdk.v2.ontologies.errors import MediaUploadPropertyNotBackedByMediaSetView` | **Ontologies** | MissingParameter | `from foundry_sdk.v2.ontologies.errors import MissingParameter` | **Ontologies** | MissingValueTypeReference | `from foundry_sdk.v2.ontologies.errors import MissingValueTypeReference` | **Ontologies** | MultipleGroupByOnFieldNotSupported | `from foundry_sdk.v2.ontologies.errors import MultipleGroupByOnFieldNotSupported` | +**Ontologies** | MultipleMediaUploadDestinations | `from foundry_sdk.v2.ontologies.errors import MultipleMediaUploadDestinations` | **Ontologies** | MultiplePropertyValuesNotSupported | `from foundry_sdk.v2.ontologies.errors import MultiplePropertyValuesNotSupported` | **Ontologies** | NotCipherFormatted | `from foundry_sdk.v2.ontologies.errors import NotCipherFormatted` | **Ontologies** | ObjectAlreadyExists | `from foundry_sdk.v2.ontologies.errors import ObjectAlreadyExists` | @@ -4172,6 +4186,7 @@ Namespace | Name | Import | **Ontologies** | ActionTypeNotFound | `from foundry_sdk.v1.ontologies.errors import ActionTypeNotFound` | **Ontologies** | ActionValidationFailed | `from foundry_sdk.v1.ontologies.errors import ActionValidationFailed` | **Ontologies** | AggregationAccuracyNotSupported | `from foundry_sdk.v1.ontologies.errors import AggregationAccuracyNotSupported` | +**Ontologies** | AggregationDepthExceededLimit | `from foundry_sdk.v1.ontologies.errors import AggregationDepthExceededLimit` | **Ontologies** | AggregationGroupCountExceededLimit | `from foundry_sdk.v1.ontologies.errors import AggregationGroupCountExceededLimit` | **Ontologies** | AggregationMemoryExceededLimit | `from foundry_sdk.v1.ontologies.errors import AggregationMemoryExceededLimit` | **Ontologies** | AggregationMetricNotSupported | `from foundry_sdk.v1.ontologies.errors import AggregationMetricNotSupported` | @@ -4250,9 +4265,12 @@ Namespace | Name | Import | **Ontologies** | MarketplaceSdkObjectMappingNotFound | `from foundry_sdk.v1.ontologies.errors import MarketplaceSdkObjectMappingNotFound` | **Ontologies** | MarketplaceSdkPropertyMappingNotFound | `from foundry_sdk.v1.ontologies.errors import MarketplaceSdkPropertyMappingNotFound` | **Ontologies** | MarketplaceSdkQueryMappingNotFound | `from foundry_sdk.v1.ontologies.errors import MarketplaceSdkQueryMappingNotFound` | +**Ontologies** | MediaUploadDestinationNotConfigured | `from foundry_sdk.v1.ontologies.errors import MediaUploadDestinationNotConfigured` | +**Ontologies** | MediaUploadPropertyNotBackedByMediaSetView | `from foundry_sdk.v1.ontologies.errors import MediaUploadPropertyNotBackedByMediaSetView` | **Ontologies** | MissingParameter | `from foundry_sdk.v1.ontologies.errors import MissingParameter` | **Ontologies** | MissingValueTypeReference | `from foundry_sdk.v1.ontologies.errors import MissingValueTypeReference` | **Ontologies** | MultipleGroupByOnFieldNotSupported | `from foundry_sdk.v1.ontologies.errors import MultipleGroupByOnFieldNotSupported` | +**Ontologies** | MultipleMediaUploadDestinations | `from foundry_sdk.v1.ontologies.errors import MultipleMediaUploadDestinations` | **Ontologies** | MultiplePropertyValuesNotSupported | `from foundry_sdk.v1.ontologies.errors import MultiplePropertyValuesNotSupported` | **Ontologies** | NotCipherFormatted | `from foundry_sdk.v1.ontologies.errors import NotCipherFormatted` | **Ontologies** | ObjectAlreadyExists | `from foundry_sdk.v1.ontologies.errors import ObjectAlreadyExists` | diff --git a/docs-snippets-npm/package.json b/docs-snippets-npm/package.json index 3db29e720..40ddb5ace 100644 --- a/docs-snippets-npm/package.json +++ b/docs-snippets-npm/package.json @@ -24,7 +24,7 @@ "sls": { "dependencies": { "com.palantir.foundry.api:api-gateway": { - "minVersion": "1.1665.0", + "minVersion": "1.1680.0", "maxVersion": "1.x.x", "optional": false } diff --git a/docs-snippets-npm/src/index.ts b/docs-snippets-npm/src/index.ts index c03466f6d..ee9202552 100644 --- a/docs-snippets-npm/src/index.ts +++ b/docs-snippets-npm/src/index.ts @@ -1145,12 +1145,12 @@ export const PYTHON_PLATFORM_SNIPPETS: SdkSnippets Callable[AnyParameters, "SseContextManager[R]"]: + return cast( + "Callable[AnyParameters, SseContextManager[R]]", + functools.partial(func, _sdk_internal={"response_mode": "SSE"}), # type: ignore + ) + + +def async_with_sse_response( + # See explanation in "with_raw_response" for why we need to the "response_type" parameter + response_type: Callable[[R], None], + func: Callable[AnyParameters, Any], +) -> Callable[AnyParameters, "AsyncSseContextManager[R]"]: + return cast( + "Callable[AnyParameters, AsyncSseContextManager[R]]", + functools.partial(func, _sdk_internal={"response_mode": "SSE"}), # type: ignore + ) + + +ResponseMode = Literal[ + "DECODED", "ITERATOR", "RAW", "STREAMING", "ARROW_TABLE", "PARQUET_TABLE", "SSE" +] + +# Response modes whose body is streamed off the wire rather than buffered. These must enable httpx +# streaming on the request and be fully read before an error response can be inspected. +_STREAMED_RESPONSE_MODES = ("STREAMING", "SSE") # The SdkInternal dictionary is a flexible way to pass additional information to the API client @@ -204,6 +238,29 @@ def _get_type_adapter(_type: ValueType) -> pydantic.TypeAdapter: return pydantic.TypeAdapter(_type) +def _decode_value(response_type: ValueType, data: Any) -> Any: + """Deserialize an already-parsed JSON value into ``response_type``. + + Shared by ``BaseApiResponse.decode`` (whole-body decoding) and the SSE runtime (per-event + decoding). ``data`` must already be a parsed JSON value (e.g. from ``json.loads``). + """ + _, _type = _get_is_optional(response_type) + origin_type = _get_annotated_origin(_type) + + if _type is None: + return None + + if origin_type is Any: + return data + + # Check if the type is a BaseModel class + if isclass(origin_type) and issubclass(origin_type, pydantic.BaseModel): + return origin_type.model_validate(data) + + adapter = _get_type_adapter(_type) + return adapter.validate_python(data) + + @dataclass(frozen=True) class RequestInfo: method: str @@ -309,17 +366,7 @@ def decode(self) -> T: if origin_type is bytes: return cast(T, self._response.content) - data = self.json() - - if origin_type is Any: - return data - - # Check if the type is a BaseModel class - if isclass(origin_type) and issubclass(origin_type, pydantic.BaseModel): - return cast(T, origin_type.model_validate(data)) - - adapter = _get_type_adapter(_type) - return cast(T, adapter.validate_python(data)) + return cast(T, _decode_value(self._request_info.response_type, self.json())) class ApiResponse(Generic[T], BaseApiResponse[T]): @@ -730,7 +777,7 @@ def make_request(token: Token): return self._session.send( request=request, - stream=response_mode == "STREAMING", + stream=response_mode in _STREAMED_RESPONSE_MODES, ) res = self._auth.execute_with_token(make_request) @@ -740,6 +787,10 @@ def make_request(token: Token): if response_mode == "STREAMING": return StreamingContextManager(request_info, api_response) + elif response_mode == "SSE": + from foundry_sdk._core.sse import SseContextManager + + return SseContextManager(request_info, api_response) elif response_mode == "ARROW_TABLE": if res.content == b"": return None @@ -763,7 +814,7 @@ def _check_for_errors(self, request_info: RequestInfo, res: httpx.Response): # wait for the entire response to be streamed back before we can access # the content. If we don't do this, accessing "text" or calling ".json()" # will raise an exception. - if request_info.response_mode == "STREAMING": + if request_info.response_mode in _STREAMED_RESPONSE_MODES: res.read() self._handle_error(request_info, res) @@ -819,6 +870,13 @@ async def fetch_page( request_info, self._async_call_api(request_info, response_mode="STREAMING"), ) + elif response_mode == "SSE": + from foundry_sdk._core.sse import AsyncSseContextManager + + return AsyncSseContextManager( + request_info, + self._async_call_api(request_info, response_mode="SSE"), + ) else: return self._async_call_api(request_info, response_mode) @@ -835,14 +893,16 @@ async def make_request(token: Token): timeout=self._get_timeout(request_info), ) - return await self._client.send(request=request, stream=response_mode == "STREAMING") + return await self._client.send( + request=request, stream=response_mode in _STREAMED_RESPONSE_MODES + ) res = await self._auth.execute_with_token(make_request) await self._check_for_errors(request_info, res) api_response: AsyncApiResponse[Any] = AsyncApiResponse(request_info, res) - if response_mode == "RAW" or response_mode == "STREAMING": + if response_mode in ("RAW", *_STREAMED_RESPONSE_MODES): return api_response elif response_mode == "ARROW_TABLE": if res.content == b"": @@ -865,7 +925,7 @@ async def _check_for_errors(self, request_info: RequestInfo, res: httpx.Response # wait for the entire response to be streamed back before we can access # the content. If we don't do this, accessing "text" or calling ".json()" # will raise an exception. - if request_info.response_mode == "STREAMING": + if request_info.response_mode in _STREAMED_RESPONSE_MODES: await res.aread() self._handle_error(request_info, res) diff --git a/foundry_sdk/_core/sse.py b/foundry_sdk/_core/sse.py new file mode 100644 index 000000000..068c475df --- /dev/null +++ b/foundry_sdk/_core/sse.py @@ -0,0 +1,163 @@ +# Copyright 2024 Palantir Technologies, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import annotations + +import json +from dataclasses import dataclass +from typing import Any +from typing import AsyncIterator +from typing import Awaitable +from typing import Generic +from typing import Iterator +from typing import Optional +from typing import Type +from typing import TypeVar + +import httpx +import pydantic +from httpx_sse import EventSource +from httpx_sse import ServerSentEvent +from httpx_sse import SSEError + +from foundry_sdk._core.api_client import ApiResponse +from foundry_sdk._core.api_client import AsyncApiResponse +from foundry_sdk._core.api_client import RequestInfo +from foundry_sdk._core.api_client import _decode_value +from foundry_sdk._errors import SseContentTypeError +from foundry_sdk._errors import SseEventDecodeError +from foundry_sdk._errors import StreamConsumedError + +T = TypeVar("T") + + +@dataclass +class SseEvent(Generic[T]): + """A single parsed Server-Sent Event. + + ``data`` is the deserialized event payload (decoded into the operation's event type). The + ``event``, ``id`` and ``retry`` fields carry the raw SSE framing for callers that need it. + """ + + data: T + event: str + id: str + retry: Optional[int] + + +def _decode_sse_event(request_info: RequestInfo, sse: ServerSentEvent) -> "SseEvent[Any]": + """Decode a raw ``ServerSentEvent`` into a typed ``SseEvent`` using the operation's event type. + + :raises SseEventDecodeError: if the event data is not valid JSON or does not match the event type. + """ + try: + data = _decode_value(request_info.response_type, json.loads(sse.data)) + except (json.JSONDecodeError, pydantic.ValidationError) as e: + raise SseEventDecodeError(f"Failed to decode SSE event data: {e}") from e + return SseEvent( + data=data, + event=sse.event, + id=sse.id, + retry=sse.retry, + ) + + +class SseApiResponse(Generic[T], ApiResponse[T]): + def __init__(self, request_info: RequestInfo, response: httpx.Response): + super().__init__(request_info, response) + + def iter_sse(self) -> Iterator[SseEvent[T]]: + """Yield typed events from the stream as they arrive. + + :raises SseContentTypeError: if the response is not a ``text/event-stream``. + """ + source = EventSource(self._response) + try: + for sse in source.iter_sse(): + if not sse.data: + # Empty-data dispatches (a lone ``id:`` or an ``event:``-only terminator) carry no + # JSON payload to decode, so skip them rather than feeding "" to ``json.loads``. + continue + yield _decode_sse_event(self._request_info, sse) + except SSEError as e: + raise SseContentTypeError(str(e)) from e + except httpx.StreamConsumed as e: + raise StreamConsumedError(str(e)) from e + + def __iter__(self) -> Iterator[SseEvent[T]]: + return self.iter_sse() + + +class AsyncSseApiResponse(Generic[T], AsyncApiResponse[T]): + def __init__(self, request_info: RequestInfo, response: httpx.Response): + super().__init__(request_info, response) + + async def aiter_sse(self) -> AsyncIterator[SseEvent[T]]: + """Yield typed events from the stream as they arrive. + + :raises SseContentTypeError: if the response is not a ``text/event-stream``. + """ + source = EventSource(self._response) + try: + async for sse in source.aiter_sse(): + if not sse.data: + # Empty-data dispatches (a lone ``id:`` or an ``event:``-only terminator) carry no + # JSON payload to decode, so skip them rather than feeding "" to ``json.loads``. + continue + yield _decode_sse_event(self._request_info, sse) + except SSEError as e: + raise SseContentTypeError(str(e)) from e + except httpx.StreamConsumed as e: + raise StreamConsumedError(str(e)) from e + + def __aiter__(self) -> AsyncIterator[SseEvent[T]]: + return self.aiter_sse() + + +class SseContextManager(Generic[T]): + def __init__(self, request_info: RequestInfo, response: ApiResponse): + self._request_info = request_info + self._response = response + + def __enter__(self) -> SseApiResponse[T]: + return SseApiResponse[T](self._request_info, self._response._response) + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[Any], + ) -> None: + self._response.close() + + +class AsyncSseContextManager(Generic[T]): + def __init__(self, request_info: RequestInfo, response: Awaitable[AsyncApiResponse]): + self._request_info = request_info + self._awaitable_response = response + self._response: Optional[AsyncApiResponse] = None + + async def __aenter__(self) -> AsyncSseApiResponse[T]: + self._response = await self._awaitable_response + return AsyncSseApiResponse[T](self._request_info, self._response._response) + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[Any], + ) -> None: + if self._response is not None: + await self._response.aclose() diff --git a/foundry_sdk/_errors/__init__.py b/foundry_sdk/_errors/__init__.py index a26f31e76..608314a54 100644 --- a/foundry_sdk/_errors/__init__.py +++ b/foundry_sdk/_errors/__init__.py @@ -51,6 +51,8 @@ ) # NOQA from foundry_sdk._errors.sdk_internal_error import SDKInternalError as SDKInternalError from foundry_sdk._errors.sdk_internal_error import handle_unexpected as handle_unexpected # NOQA +from foundry_sdk._errors.sse_error import SseContentTypeError as SseContentTypeError +from foundry_sdk._errors.sse_error import SseEventDecodeError as SseEventDecodeError from foundry_sdk._errors.stream_error import StreamConsumedError as StreamConsumedError from foundry_sdk._errors.timeout_error import ConnectTimeout as ConnectTimeout from foundry_sdk._errors.timeout_error import ReadTimeout as ReadTimeout diff --git a/foundry_sdk/_errors/sse_error.py b/foundry_sdk/_errors/sse_error.py new file mode 100644 index 000000000..e4181c90b --- /dev/null +++ b/foundry_sdk/_errors/sse_error.py @@ -0,0 +1,24 @@ +# Copyright 2024 Palantir Technologies, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from foundry_sdk._errors.palantir_exception import PalantirException + + +class SseContentTypeError(PalantirException): + """The server response was not a ``text/event-stream`` as required for SSE consumption.""" + + +class SseEventDecodeError(PalantirException): + """An SSE event's ``data`` could not be parsed as JSON or decoded into the expected event type.""" diff --git a/foundry_sdk/_version.py b/foundry_sdk/_version.py index d9901f112..861469d5d 100644 --- a/foundry_sdk/_version.py +++ b/foundry_sdk/_version.py @@ -17,4 +17,4 @@ # using the autorelease bot __version__ = "0.0.0" -__openapi_document_version__ = "1.1665.0" +__openapi_document_version__ = "1.1680.0" diff --git a/foundry_sdk/v1/ontologies/errors.py b/foundry_sdk/v1/ontologies/errors.py index 2b0da4043..1c8773c08 100644 --- a/foundry_sdk/v1/ontologies/errors.py +++ b/foundry_sdk/v1/ontologies/errors.py @@ -190,6 +190,25 @@ class AggregationAccuracyNotSupported(errors.BadRequestError): error_instance_id: str +class AggregationDepthExceededLimitParameters(typing_extensions.TypedDict): + """ + The aggregation request contains too many levels of nested groupings. This can be fixed by reducing the + number of nested groupings in your request. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + depth: int + depthLimit: int + + +@dataclass +class AggregationDepthExceededLimit(errors.BadRequestError): + name: typing.Literal["AggregationDepthExceededLimit"] + parameters: AggregationDepthExceededLimitParameters + error_instance_id: str + + class AggregationGroupCountExceededLimitParameters(typing_extensions.TypedDict): """ The number of groups in the aggregations grouping exceeded the allowed limit. This can typically be fixed by @@ -1496,6 +1515,45 @@ class MarketplaceSdkQueryMappingNotFound(errors.NotFoundError): error_instance_id: str +class MediaUploadDestinationNotConfiguredParameters(typing_extensions.TypedDict): + """ + The media reference property is backed by multiple media set views, and none of them are marked as the upload + destination for this property. Set an upload destination on exactly one of the backing media set views for + this property in Ontology Manager. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + objectType: ontologies_models.ObjectTypeApiName + property: ontologies_models.PropertyApiName + + +@dataclass +class MediaUploadDestinationNotConfigured(errors.BadRequestError): + name: typing.Literal["MediaUploadDestinationNotConfigured"] + parameters: MediaUploadDestinationNotConfiguredParameters + error_instance_id: str + + +class MediaUploadPropertyNotBackedByMediaSetViewParameters(typing_extensions.TypedDict): + """ + The property is not backed by any media set view datasource and cannot accept media uploads. Add a media set + view datasource that includes this property in Ontology Manager. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + objectType: ontologies_models.ObjectTypeApiName + property: ontologies_models.PropertyApiName + + +@dataclass +class MediaUploadPropertyNotBackedByMediaSetView(errors.BadRequestError): + name: typing.Literal["MediaUploadPropertyNotBackedByMediaSetView"] + parameters: MediaUploadPropertyNotBackedByMediaSetViewParameters + error_instance_id: str + + class MissingParameterParameters(typing_extensions.TypedDict): """ Required parameters are missing. Please look at the `parameters` field to see which required parameters are @@ -1545,6 +1603,26 @@ class MultipleGroupByOnFieldNotSupported(errors.BadRequestError): error_instance_id: str +class MultipleMediaUploadDestinationsParameters(typing_extensions.TypedDict): + """ + The media reference property has multiple media set views marked as upload destinations. At most one media + source per property should be configured as the upload destination. This typically indicates an inconsistent + object type configuration; review the backing media sources for this property in Ontology Manager. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + objectType: ontologies_models.ObjectTypeApiName + property: ontologies_models.PropertyApiName + + +@dataclass +class MultipleMediaUploadDestinations(errors.BadRequestError): + name: typing.Literal["MultipleMediaUploadDestinations"] + parameters: MultipleMediaUploadDestinationsParameters + error_instance_id: str + + class MultiplePropertyValuesNotSupportedParameters(typing_extensions.TypedDict): """ One of the requested property filters does not support multiple values. Please include only a single value for @@ -2518,6 +2596,7 @@ class ViewObjectPermissionDenied(errors.PermissionDeniedError): "ActionTypeNotFound", "ActionValidationFailed", "AggregationAccuracyNotSupported", + "AggregationDepthExceededLimit", "AggregationGroupCountExceededLimit", "AggregationMemoryExceededLimit", "AggregationMetricNotSupported", @@ -2596,9 +2675,12 @@ class ViewObjectPermissionDenied(errors.PermissionDeniedError): "MarketplaceSdkObjectMappingNotFound", "MarketplaceSdkPropertyMappingNotFound", "MarketplaceSdkQueryMappingNotFound", + "MediaUploadDestinationNotConfigured", + "MediaUploadPropertyNotBackedByMediaSetView", "MissingParameter", "MissingValueTypeReference", "MultipleGroupByOnFieldNotSupported", + "MultipleMediaUploadDestinations", "MultiplePropertyValuesNotSupported", "NotCipherFormatted", "ObjectAlreadyExists", diff --git a/foundry_sdk/v2/cli.py b/foundry_sdk/v2/cli.py index 57107edf9..e0cc45957 100644 --- a/foundry_sdk/v2/cli.py +++ b/foundry_sdk/v2/cli.py @@ -5644,7 +5644,7 @@ def functions_value_type_version_id_op_get( preview: typing.Optional[bool], ): """ - Gets a specific value type with the given RID. The specified version is returned. + Gets a specific version of a value type with the given RID and version ID. """ result = client.functions.ValueType.VersionId.get( @@ -5705,12 +5705,14 @@ def functions_query_op_execute( version: typing.Optional[str], ): """ - Executes a Query using the given parameters. By default, this executes the latest version of the query. + Executes a Query and returns the result as a single JSON object. By default, this executes + the latest version of the query. The latest version is the one that was most recently + published, which may be a pre-release version. - This endpoint is maintained for backward compatibility only. - - For all new implementations, use the `streamingExecute` endpoint, which supports all function types - and provides enhanced functionality. + This endpoint executes global (non-ontology-scoped) query functions. For ontology-scoped + functions, use the equivalent endpoint under + `/v2/ontologies/{ontology}/queries/{queryApiName}/execute`. For streaming or incremental + result delivery, use `streamingExecute`. """ result = client.functions.Query.execute( @@ -5946,34 +5948,116 @@ def functions_query_op_streaming_execute( version: typing.Optional[str], ): """ - Executes a Query using the given parameters, returning results as an NDJSON stream. By default, this executes the latest version of the query. - - This endpoint supports all Query functions. The endpoint name 'streamingExecute' refers to the NDJSON - streaming response format. Both streaming and non-streaming functions can use this endpoint. - Non-streaming functions return a single-line NDJSON response, while streaming functions return multi-line NDJSON responses. - This is the recommended endpoint for all query execution. + Executes a Query and returns results as a Server-Sent Events (`text/event-stream`) stream. + By default, this executes the latest version of the query. The latest version is the one + that was most recently published, which may be a pre-release version. - The response is returned as a binary stream in NDJSON (Newline Delimited JSON) format, where each line - is a StreamingExecuteQueryResponse containing either a data batch or an error. + This endpoint supports all Query functions. Each SSE event's `data` field is a JSON-encoded + `StreamingExecuteQueryResponse` – either a data batch (`type: data`) carrying one or more + result values, or an error (`type: error`) emitted before stream termination if execution + fails. Non-streaming functions emit a single data event containing the entire result; + streaming functions emit a data event per batch as results become available. - For a function returning a list of 5 records with a batch size of 3, the response stream would contain - two lines. The first line contains the first 3 items, and the second line contains the remaining 2 items: + Per the Server-Sent Events specification, each event is terminated by a blank line: ``` - {"type":"data","value":[{"productId":"SKU-001","price":29.99},{"productId":"SKU-002","price":49.99},{"productId":"SKU-003","price":19.99}]} - {"type":"data","value":[{"productId":"SKU-004","price":39.99},{"productId":"SKU-005","price":59.99}]} + data: {"type":"data","value":[{"productId":"SKU-001","price":29.99}]} + + data: {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} + ``` - Each line is a separate JSON object followed by a newline character. Clients should parse the stream - line-by-line to process results as they arrive. If an error occurs during execution, the stream will - contain an error line: + """ + result = client.functions.Query.streaming_execute( + query_api_name=query_api_name, + parameters=json.loads(parameters), + attribution=attribution, + branch=branch, + ontology=ontology, + preview=preview, + trace_parent=trace_parent, + trace_state=trace_state, + transaction_id=transaction_id, + version=version, + ) + click.echo(repr(result)) + + +@functions_query.command("streaming_execute_events") +@click.argument("query_api_name", type=str, required=True) +@click.option("--parameters", type=str, required=True, help="""""") +@click.option("--attribution", type=str, required=False, help="""""") +@click.option( + "--branch", + type=str, + required=False, + help="""The Foundry branch to execute the query from. If not specified, the default branch is used. +When provided without `version`, the latest version on this branch is used. +When provided with `version`, the specified version must exist on the branch. +""", +) +@click.option( + "--ontology", + type=str, + required=False, + help="""Optional ontology identifier (RID or API name). When provided, executes an ontology-scoped +function. When omitted, executes a global function. +""", +) +@click.option( + "--preview", type=bool, required=False, help="""Enables the use of preview functionality.""" +) +@click.option("--trace_parent", type=str, required=False, help="""""") +@click.option("--trace_state", type=str, required=False, help="""""") +@click.option( + "--transaction_id", + type=str, + required=False, + help="""The ID of a transaction to read from. Transactions are an experimental feature and not all workflows may be supported.""", +) +@click.option( + "--version", + type=str, + required=False, + help="""The version of the query to execute. When used with `branch`, the specified version must exist on the branch. +""", +) +@click.pass_obj +def functions_query_op_streaming_execute_events( + client: FoundryClient, + query_api_name: str, + parameters: str, + attribution: typing.Optional[str], + branch: typing.Optional[str], + ontology: typing.Optional[str], + preview: typing.Optional[bool], + trace_parent: typing.Optional[str], + trace_state: typing.Optional[str], + transaction_id: typing.Optional[str], + version: typing.Optional[str], +): + """ + Executes a Query and returns results as a Server-Sent Events (`text/event-stream`) stream. + By default, this executes the latest version of the query. The latest version is the one + that was most recently published, which may be a pre-release version. + + This endpoint supports all Query functions. Each SSE event's `data` field is a JSON-encoded + `StreamingExecuteQueryResponse` – either a data batch (`type: data`) carrying one or more + result values, or an error (`type: error`) emitted before stream termination if execution + fails. Non-streaming functions emit a single data event containing the entire result; + streaming functions emit a data event per batch as results become available. + + Per the Server-Sent Events specification, each event is terminated by a blank line: ``` - {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} + data: {"type":"data","value":[{"productId":"SKU-001","price":29.99}]} + + data: {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} + ``` """ - result = client.functions.Query.streaming_execute( + result = client.functions.Query.streaming_execute_events( query_api_name=query_api_name, parameters=json.loads(parameters), attribution=attribution, @@ -5985,7 +6069,7 @@ def functions_query_op_streaming_execute( transaction_id=transaction_id, version=version, ) - click.echo(result) + click.echo(repr(result)) @functions.group("execution") @@ -8094,6 +8178,13 @@ def ontologies_time_series_value_bank_property(): @click.argument("object_type", type=str, required=True) @click.argument("primary_key", type=str, required=True) @click.argument("property_name", type=str, required=True) +@click.option( + "--branch", + type=str, + required=False, + help="""The Foundry branch to read from. If not specified, the default branch will be used. +""", +) @click.option( "--sdk_package_rid", type=str, @@ -8115,6 +8206,7 @@ def ontologies_time_series_value_bank_property_op_get_latest_value( object_type: str, primary_key: str, property_name: str, + branch: typing.Optional[str], sdk_package_rid: typing.Optional[str], sdk_version: typing.Optional[str], ): @@ -8127,6 +8219,7 @@ def ontologies_time_series_value_bank_property_op_get_latest_value( object_type=object_type, primary_key=primary_key, property_name=property_name, + branch=branch, sdk_package_rid=sdk_package_rid, sdk_version=sdk_version, ) @@ -8138,6 +8231,13 @@ def ontologies_time_series_value_bank_property_op_get_latest_value( @click.argument("object_type", type=str, required=True) @click.argument("primary_key", type=str, required=True) @click.argument("property", type=str, required=True) +@click.option( + "--branch", + type=str, + required=False, + help="""The Foundry branch to read from. If not specified, the default branch will be used. +""", +) @click.option("--range", type=str, required=False, help="""""") @click.option( "--sdk_package_rid", @@ -8160,6 +8260,7 @@ def ontologies_time_series_value_bank_property_op_stream_values( object_type: str, primary_key: str, property: str, + branch: typing.Optional[str], range: typing.Optional[str], sdk_package_rid: typing.Optional[str], sdk_version: typing.Optional[str], @@ -8173,6 +8274,7 @@ def ontologies_time_series_value_bank_property_op_stream_values( object_type=object_type, primary_key=primary_key, property=property, + branch=branch, range=None if range is None else json.loads(range), sdk_package_rid=sdk_package_rid, sdk_version=sdk_version, @@ -11335,6 +11437,9 @@ def ontologies_ontology_object_type_op_list( more results available, at least one result will be present in the response. + Note: the `aliases` field is not populated on this endpoint and will always be empty. To retrieve object type + aliases, use the get-by-RID read paths (e.g. `getObjectTypeV2`). + """ result = client.ontologies.Ontology.ObjectType.list( ontology=ontology, @@ -12063,6 +12168,13 @@ def ontologies_cipher_text_property(): @click.argument("object_type", type=str, required=True) @click.argument("primary_key", type=str, required=True) @click.argument("property", type=str, required=True) +@click.option( + "--branch", + type=str, + required=False, + help="""The Foundry branch to read from. If not specified, the default branch will be used. +""", +) @click.pass_obj def ontologies_cipher_text_property_op_decrypt( client: FoundryClient, @@ -12070,6 +12182,7 @@ def ontologies_cipher_text_property_op_decrypt( object_type: str, primary_key: str, property: str, + branch: typing.Optional[str], ): """ Decrypt the value of a ciphertext property. @@ -12080,6 +12193,7 @@ def ontologies_cipher_text_property_op_decrypt( object_type=object_type, primary_key=primary_key, property=property, + branch=branch, ) click.echo(repr(result)) @@ -13490,6 +13604,13 @@ def sql_queries_sql_query_op_execute( help="""The SQL query to execute. """, ) +@click.option( + "--branch", + type=str, + required=False, + help="""The Foundry branch to execute the query against. If not specified, the default (main) branch is used. +""", +) @click.option( "--dry_run", type=bool, @@ -13515,14 +13636,23 @@ def sql_queries_sql_query_op_execute( help="""Maximum number of rows to return. """, ) +@click.option( + "--scenario_rid", + type=str, + required=False, + help="""The scenario to evaluate the query against. If not specified, no scenario is applied. +""", +) @click.pass_obj def sql_queries_sql_query_op_execute_ontology( client: FoundryClient, query: str, + branch: typing.Optional[str], dry_run: typing.Optional[bool], parameters: typing.Optional[str], preview: typing.Optional[bool], row_limit: typing.Optional[int], + scenario_rid: typing.Optional[str], ): """ Executes a SQL query against the Ontology. Results are returned synchronously in @@ -13531,10 +13661,12 @@ def sql_queries_sql_query_op_execute_ontology( """ result = client.sql_queries.SqlQuery.execute_ontology( query=query, + branch=branch, dry_run=dry_run, parameters=None if parameters is None else json.loads(parameters), preview=preview, row_limit=row_limit, + scenario_rid=scenario_rid, ) click.echo(result) diff --git a/foundry_sdk/v2/functions/models.py b/foundry_sdk/v2/functions/models.py index e975a8e5b..235add460 100644 --- a/foundry_sdk/v2/functions/models.py +++ b/foundry_sdk/v2/functions/models.py @@ -401,6 +401,27 @@ class RunningExecution(core.ModelBase): type: typing.Literal["running"] = "running" +class StreamingExecuteEventsQueryRequest(core.ModelBase): + """StreamingExecuteEventsQueryRequest""" + + ontology: typing.Optional[ontologies_models.OntologyIdentifier] = None + """ + Optional ontology identifier (RID or API name). When provided, executes an ontology-scoped + function. When omitted, executes a global function. + """ + + parameters: typing.Dict[ParameterId, typing.Optional[DataValue]] + version: typing.Optional[FunctionVersion] = None + """The version of the query to execute. When used with `branch`, the specified version must exist on the branch.""" + + branch: typing.Optional[core_models.FoundryBranch] = None + """ + The Foundry branch to execute the query from. If not specified, the default branch is used. + When provided without `version`, the latest version on this branch is used. + When provided with `version`, the specified version must exist on the branch. + """ + + class StreamingExecuteQueryRequest(core.ModelBase): """StreamingExecuteQueryRequest""" @@ -422,6 +443,30 @@ class StreamingExecuteQueryRequest(core.ModelBase): """ +StreamingExecuteQueryResponse: typing_extensions.TypeAlias = typing_extensions.Annotated[ + typing.Union["StreamingQueryData", "StreamingQueryError"], pydantic.Field(discriminator="type") +] +"""A single message in a streaming Query execution response. Each message contains either a data batch or an error.""" + + +class StreamingQueryData(core.ModelBase): + """A batch of query results.""" + + value: DataValue + type: typing.Literal["data"] = "data" + + +class StreamingQueryError(core.ModelBase): + """An error that occurred during query execution.""" + + error_code: str = pydantic.Field(alias=str("errorCode")) # type: ignore[literal-required] + error_name: str = pydantic.Field(alias=str("errorName")) # type: ignore[literal-required] + error_instance_id: str = pydantic.Field(alias=str("errorInstanceId")) # type: ignore[literal-required] + error_description: typing.Optional[str] = pydantic.Field(alias=str("errorDescription"), default=None) # type: ignore[literal-required] + parameters: typing.Dict[str, typing.Any] + type: typing.Literal["error"] = "error" + + class StructConstraint(core.ModelBase): """StructConstraint""" @@ -754,7 +799,11 @@ class VersionId(core.ModelBase): "RegexConstraint", "RidConstraint", "RunningExecution", + "StreamingExecuteEventsQueryRequest", "StreamingExecuteQueryRequest", + "StreamingExecuteQueryResponse", + "StreamingQueryData", + "StreamingQueryError", "StructConstraint", "StructFieldApiName", "StructFieldName", diff --git a/foundry_sdk/v2/functions/query.py b/foundry_sdk/v2/functions/query.py index 53639392f..49adc14c6 100644 --- a/foundry_sdk/v2/functions/query.py +++ b/foundry_sdk/v2/functions/query.py @@ -77,12 +77,14 @@ def execute( _sdk_internal: core.SdkInternal = {}, ) -> functions_models.ExecuteQueryResponse: """ - Executes a Query using the given parameters. By default, this executes the latest version of the query. + Executes a Query and returns the result as a single JSON object. By default, this executes + the latest version of the query. The latest version is the one that was most recently + published, which may be a pre-release version. - This endpoint is maintained for backward compatibility only. - - For all new implementations, use the `streamingExecute` endpoint, which supports all function types - and provides enhanced functionality. + This endpoint executes global (non-ontology-scoped) query functions. For ontology-scoped + functions, use the equivalent endpoint under + `/v2/ontologies/{ontology}/queries/{queryApiName}/execute`. For streaming or incremental + result delivery, use `streamingExecute`. :param query_api_name: :type query_api_name: QueryApiName @@ -411,32 +413,25 @@ def streaming_execute( version: typing.Optional[functions_models.FunctionVersion] = None, request_timeout: typing.Optional[core.Timeout] = None, _sdk_internal: core.SdkInternal = {}, - ) -> bytes: + ) -> core.SseContextManager[functions_models.StreamingExecuteQueryResponse]: """ - Executes a Query using the given parameters, returning results as an NDJSON stream. By default, this executes the latest version of the query. - - This endpoint supports all Query functions. The endpoint name 'streamingExecute' refers to the NDJSON - streaming response format. Both streaming and non-streaming functions can use this endpoint. - Non-streaming functions return a single-line NDJSON response, while streaming functions return multi-line NDJSON responses. - This is the recommended endpoint for all query execution. + Executes a Query and returns results as a Server-Sent Events (`text/event-stream`) stream. + By default, this executes the latest version of the query. The latest version is the one + that was most recently published, which may be a pre-release version. - The response is returned as a binary stream in NDJSON (Newline Delimited JSON) format, where each line - is a StreamingExecuteQueryResponse containing either a data batch or an error. + This endpoint supports all Query functions. Each SSE event's `data` field is a JSON-encoded + `StreamingExecuteQueryResponse` – either a data batch (`type: data`) carrying one or more + result values, or an error (`type: error`) emitted before stream termination if execution + fails. Non-streaming functions emit a single data event containing the entire result; + streaming functions emit a data event per batch as results become available. - For a function returning a list of 5 records with a batch size of 3, the response stream would contain - two lines. The first line contains the first 3 items, and the second line contains the remaining 2 items: + Per the Server-Sent Events specification, each event is terminated by a blank line: ``` - {"type":"data","value":[{"productId":"SKU-001","price":29.99},{"productId":"SKU-002","price":49.99},{"productId":"SKU-003","price":19.99}]} - {"type":"data","value":[{"productId":"SKU-004","price":39.99},{"productId":"SKU-005","price":59.99}]} - ``` + data: {"type":"data","value":[{"productId":"SKU-001","price":29.99}]} - Each line is a separate JSON object followed by a newline character. Clients should parse the stream - line-by-line to process results as they arrive. If an error occurs during execution, the stream will - contain an error line: + data: {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} - ``` - {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} ``` :param query_api_name: @@ -462,7 +457,7 @@ def streaming_execute( :param request_timeout: timeout setting for this request in seconds. :type request_timeout: Optional[int] :return: Returns the result object. - :rtype: bytes + :rtype: core.SseContextManager[functions_models.StreamingExecuteQueryResponse] :raises StreamingExecuteQueryPermissionDenied: Could not streamingExecute the Query. """ @@ -483,7 +478,7 @@ def streaming_execute( "traceParent": trace_parent, "traceState": trace_state, "Content-Type": "application/json", - "Accept": "application/octet-stream", + "Accept": "text/event-stream", }, body=functions_models.StreamingExecuteQueryRequest( ontology=ontology, @@ -491,12 +486,114 @@ def streaming_execute( version=version, branch=branch, ), - response_type=bytes, + response_type=functions_models.StreamingExecuteQueryResponse, request_timeout=request_timeout, throwable_errors={ "StreamingExecuteQueryPermissionDenied": functions_errors.StreamingExecuteQueryPermissionDenied, }, - response_mode=_sdk_internal.get("response_mode"), + response_mode=_sdk_internal.get("response_mode", "SSE"), + ), + ) + + @core.maybe_ignore_preview + @pydantic.validate_call + @errors.handle_unexpected + def streaming_execute_events( + self, + query_api_name: functions_models.QueryApiName, + *, + parameters: typing.Dict[ + functions_models.ParameterId, typing.Optional[functions_models.DataValue] + ], + attribution: typing.Optional[core_models.Attribution] = None, + branch: typing.Optional[core_models.FoundryBranch] = None, + ontology: typing.Optional[ontologies_models.OntologyIdentifier] = None, + preview: typing.Optional[core_models.PreviewMode] = None, + trace_parent: typing.Optional[core_models.TraceParent] = None, + trace_state: typing.Optional[core_models.TraceState] = None, + transaction_id: typing.Optional[functions_models.TransactionId] = None, + version: typing.Optional[functions_models.FunctionVersion] = None, + request_timeout: typing.Optional[core.Timeout] = None, + _sdk_internal: core.SdkInternal = {}, + ) -> core.SseContextManager[functions_models.StreamingExecuteQueryResponse]: + """ + Executes a Query and returns results as a Server-Sent Events (`text/event-stream`) stream. + By default, this executes the latest version of the query. The latest version is the one + that was most recently published, which may be a pre-release version. + + This endpoint supports all Query functions. Each SSE event's `data` field is a JSON-encoded + `StreamingExecuteQueryResponse` – either a data batch (`type: data`) carrying one or more + result values, or an error (`type: error`) emitted before stream termination if execution + fails. Non-streaming functions emit a single data event containing the entire result; + streaming functions emit a data event per batch as results become available. + + Per the Server-Sent Events specification, each event is terminated by a blank line: + + ``` + data: {"type":"data","value":[{"productId":"SKU-001","price":29.99}]} + + data: {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} + + ``` + + :param query_api_name: + :type query_api_name: QueryApiName + :param parameters: + :type parameters: Dict[ParameterId, Optional[DataValue]] + :param attribution: + :type attribution: Optional[Attribution] + :param branch: The Foundry branch to execute the query from. If not specified, the default branch is used. When provided without `version`, the latest version on this branch is used. When provided with `version`, the specified version must exist on the branch. + :type branch: Optional[FoundryBranch] + :param ontology: Optional ontology identifier (RID or API name). When provided, executes an ontology-scoped function. When omitted, executes a global function. + :type ontology: Optional[OntologyIdentifier] + :param preview: Enables the use of preview functionality. + :type preview: Optional[PreviewMode] + :param trace_parent: + :type trace_parent: Optional[TraceParent] + :param trace_state: + :type trace_state: Optional[TraceState] + :param transaction_id: The ID of a transaction to read from. Transactions are an experimental feature and not all workflows may be supported. + :type transaction_id: Optional[TransactionId] + :param version: The version of the query to execute. When used with `branch`, the specified version must exist on the branch. + :type version: Optional[FunctionVersion] + :param request_timeout: timeout setting for this request in seconds. + :type request_timeout: Optional[int] + :return: Returns the result object. + :rtype: core.SseContextManager[functions_models.StreamingExecuteQueryResponse] + + :raises StreamingExecuteEventsQueryPermissionDenied: Could not streamingExecuteEvents the Query. + """ + + return self._api_client.call_api( + core.RequestInfo( + method="POST", + resource_path="/v2/functions/queries/{queryApiName}/streamingExecuteEvents", + query_params={ + "preview": preview, + "transactionId": transaction_id, + }, + path_params={ + "queryApiName": query_api_name, + }, + header_params={ + "attribution": attribution, + "traceParent": trace_parent, + "traceState": trace_state, + "Content-Type": "application/json", + "Accept": "text/event-stream", + }, + body=functions_models.StreamingExecuteEventsQueryRequest( + ontology=ontology, + parameters=parameters, + version=version, + branch=branch, + ), + response_type=functions_models.StreamingExecuteQueryResponse, + request_timeout=request_timeout, + throwable_errors={ + "StreamingExecuteEventsQueryPermissionDenied": functions_errors.StreamingExecuteEventsQueryPermissionDenied, + }, + response_mode=_sdk_internal.get("response_mode", "SSE"), ), ) @@ -508,7 +605,8 @@ def execute_async(_: functions_models.ExecuteQueryAsyncResponse): ... def get(_: functions_models.Query): ... def get_by_rid(_: functions_models.Query): ... def get_by_rid_batch(_: functions_models.GetByRidQueriesBatchResponse): ... - def streaming_execute(_: bytes): ... + def streaming_execute(_: functions_models.StreamingExecuteQueryResponse): ... + def streaming_execute_events(_: functions_models.StreamingExecuteQueryResponse): ... self.execute = core.with_raw_response(execute, client.execute) self.execute_async = core.with_raw_response(execute_async, client.execute_async) @@ -516,6 +614,9 @@ def streaming_execute(_: bytes): ... self.get_by_rid = core.with_raw_response(get_by_rid, client.get_by_rid) self.get_by_rid_batch = core.with_raw_response(get_by_rid_batch, client.get_by_rid_batch) self.streaming_execute = core.with_raw_response(streaming_execute, client.streaming_execute) + self.streaming_execute_events = core.with_raw_response( + streaming_execute_events, client.streaming_execute_events + ) class _QueryClientStreaming: @@ -525,7 +626,8 @@ def execute_async(_: functions_models.ExecuteQueryAsyncResponse): ... def get(_: functions_models.Query): ... def get_by_rid(_: functions_models.Query): ... def get_by_rid_batch(_: functions_models.GetByRidQueriesBatchResponse): ... - def streaming_execute(_: bytes): ... + def streaming_execute(_: functions_models.StreamingExecuteQueryResponse): ... + def streaming_execute_events(_: functions_models.StreamingExecuteQueryResponse): ... self.execute = core.with_streaming_response(execute, client.execute) self.execute_async = core.with_streaming_response(execute_async, client.execute_async) @@ -534,8 +636,9 @@ def streaming_execute(_: bytes): ... self.get_by_rid_batch = core.with_streaming_response( get_by_rid_batch, client.get_by_rid_batch ) - self.streaming_execute = core.with_streaming_response( - streaming_execute, client.streaming_execute + self.streaming_execute = core.with_sse_response(streaming_execute, client.streaming_execute) + self.streaming_execute_events = core.with_sse_response( + streaming_execute_events, client.streaming_execute_events ) @@ -589,12 +692,14 @@ def execute( _sdk_internal: core.SdkInternal = {}, ) -> typing.Awaitable[functions_models.ExecuteQueryResponse]: """ - Executes a Query using the given parameters. By default, this executes the latest version of the query. + Executes a Query and returns the result as a single JSON object. By default, this executes + the latest version of the query. The latest version is the one that was most recently + published, which may be a pre-release version. - This endpoint is maintained for backward compatibility only. - - For all new implementations, use the `streamingExecute` endpoint, which supports all function types - and provides enhanced functionality. + This endpoint executes global (non-ontology-scoped) query functions. For ontology-scoped + functions, use the equivalent endpoint under + `/v2/ontologies/{ontology}/queries/{queryApiName}/execute`. For streaming or incremental + result delivery, use `streamingExecute`. :param query_api_name: :type query_api_name: QueryApiName @@ -923,32 +1028,25 @@ def streaming_execute( version: typing.Optional[functions_models.FunctionVersion] = None, request_timeout: typing.Optional[core.Timeout] = None, _sdk_internal: core.SdkInternal = {}, - ) -> typing.Awaitable[bytes]: + ) -> core.AsyncSseContextManager[functions_models.StreamingExecuteQueryResponse]: """ - Executes a Query using the given parameters, returning results as an NDJSON stream. By default, this executes the latest version of the query. - - This endpoint supports all Query functions. The endpoint name 'streamingExecute' refers to the NDJSON - streaming response format. Both streaming and non-streaming functions can use this endpoint. - Non-streaming functions return a single-line NDJSON response, while streaming functions return multi-line NDJSON responses. - This is the recommended endpoint for all query execution. + Executes a Query and returns results as a Server-Sent Events (`text/event-stream`) stream. + By default, this executes the latest version of the query. The latest version is the one + that was most recently published, which may be a pre-release version. - The response is returned as a binary stream in NDJSON (Newline Delimited JSON) format, where each line - is a StreamingExecuteQueryResponse containing either a data batch or an error. + This endpoint supports all Query functions. Each SSE event's `data` field is a JSON-encoded + `StreamingExecuteQueryResponse` – either a data batch (`type: data`) carrying one or more + result values, or an error (`type: error`) emitted before stream termination if execution + fails. Non-streaming functions emit a single data event containing the entire result; + streaming functions emit a data event per batch as results become available. - For a function returning a list of 5 records with a batch size of 3, the response stream would contain - two lines. The first line contains the first 3 items, and the second line contains the remaining 2 items: + Per the Server-Sent Events specification, each event is terminated by a blank line: ``` - {"type":"data","value":[{"productId":"SKU-001","price":29.99},{"productId":"SKU-002","price":49.99},{"productId":"SKU-003","price":19.99}]} - {"type":"data","value":[{"productId":"SKU-004","price":39.99},{"productId":"SKU-005","price":59.99}]} - ``` + data: {"type":"data","value":[{"productId":"SKU-001","price":29.99}]} - Each line is a separate JSON object followed by a newline character. Clients should parse the stream - line-by-line to process results as they arrive. If an error occurs during execution, the stream will - contain an error line: + data: {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} - ``` - {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} ``` :param query_api_name: @@ -974,7 +1072,7 @@ def streaming_execute( :param request_timeout: timeout setting for this request in seconds. :type request_timeout: Optional[int] :return: Returns the result object. - :rtype: typing.Awaitable[bytes] + :rtype: core.AsyncSseContextManager[functions_models.StreamingExecuteQueryResponse] :raises StreamingExecuteQueryPermissionDenied: Could not streamingExecute the Query. """ @@ -995,7 +1093,7 @@ def streaming_execute( "traceParent": trace_parent, "traceState": trace_state, "Content-Type": "application/json", - "Accept": "application/octet-stream", + "Accept": "text/event-stream", }, body=functions_models.StreamingExecuteQueryRequest( ontology=ontology, @@ -1003,12 +1101,114 @@ def streaming_execute( version=version, branch=branch, ), - response_type=bytes, + response_type=functions_models.StreamingExecuteQueryResponse, request_timeout=request_timeout, throwable_errors={ "StreamingExecuteQueryPermissionDenied": functions_errors.StreamingExecuteQueryPermissionDenied, }, - response_mode=_sdk_internal.get("response_mode"), + response_mode=_sdk_internal.get("response_mode", "SSE"), + ), + ) + + @core.maybe_ignore_preview + @pydantic.validate_call + @errors.handle_unexpected + def streaming_execute_events( + self, + query_api_name: functions_models.QueryApiName, + *, + parameters: typing.Dict[ + functions_models.ParameterId, typing.Optional[functions_models.DataValue] + ], + attribution: typing.Optional[core_models.Attribution] = None, + branch: typing.Optional[core_models.FoundryBranch] = None, + ontology: typing.Optional[ontologies_models.OntologyIdentifier] = None, + preview: typing.Optional[core_models.PreviewMode] = None, + trace_parent: typing.Optional[core_models.TraceParent] = None, + trace_state: typing.Optional[core_models.TraceState] = None, + transaction_id: typing.Optional[functions_models.TransactionId] = None, + version: typing.Optional[functions_models.FunctionVersion] = None, + request_timeout: typing.Optional[core.Timeout] = None, + _sdk_internal: core.SdkInternal = {}, + ) -> core.AsyncSseContextManager[functions_models.StreamingExecuteQueryResponse]: + """ + Executes a Query and returns results as a Server-Sent Events (`text/event-stream`) stream. + By default, this executes the latest version of the query. The latest version is the one + that was most recently published, which may be a pre-release version. + + This endpoint supports all Query functions. Each SSE event's `data` field is a JSON-encoded + `StreamingExecuteQueryResponse` – either a data batch (`type: data`) carrying one or more + result values, or an error (`type: error`) emitted before stream termination if execution + fails. Non-streaming functions emit a single data event containing the entire result; + streaming functions emit a data event per batch as results become available. + + Per the Server-Sent Events specification, each event is terminated by a blank line: + + ``` + data: {"type":"data","value":[{"productId":"SKU-001","price":29.99}]} + + data: {"type":"error","errorCode":"INVALID_ARGUMENT","errorName":"QueryRuntimeError","errorInstanceId":"3f8a9c7b-2e4d-4a1f-9b8c-7d6e5f4a3b2c","errorDescription":"Division by zero","parameters":{}} + + ``` + + :param query_api_name: + :type query_api_name: QueryApiName + :param parameters: + :type parameters: Dict[ParameterId, Optional[DataValue]] + :param attribution: + :type attribution: Optional[Attribution] + :param branch: The Foundry branch to execute the query from. If not specified, the default branch is used. When provided without `version`, the latest version on this branch is used. When provided with `version`, the specified version must exist on the branch. + :type branch: Optional[FoundryBranch] + :param ontology: Optional ontology identifier (RID or API name). When provided, executes an ontology-scoped function. When omitted, executes a global function. + :type ontology: Optional[OntologyIdentifier] + :param preview: Enables the use of preview functionality. + :type preview: Optional[PreviewMode] + :param trace_parent: + :type trace_parent: Optional[TraceParent] + :param trace_state: + :type trace_state: Optional[TraceState] + :param transaction_id: The ID of a transaction to read from. Transactions are an experimental feature and not all workflows may be supported. + :type transaction_id: Optional[TransactionId] + :param version: The version of the query to execute. When used with `branch`, the specified version must exist on the branch. + :type version: Optional[FunctionVersion] + :param request_timeout: timeout setting for this request in seconds. + :type request_timeout: Optional[int] + :return: Returns the result object. + :rtype: core.AsyncSseContextManager[functions_models.StreamingExecuteQueryResponse] + + :raises StreamingExecuteEventsQueryPermissionDenied: Could not streamingExecuteEvents the Query. + """ + + return self._api_client.call_api( + core.RequestInfo( + method="POST", + resource_path="/v2/functions/queries/{queryApiName}/streamingExecuteEvents", + query_params={ + "preview": preview, + "transactionId": transaction_id, + }, + path_params={ + "queryApiName": query_api_name, + }, + header_params={ + "attribution": attribution, + "traceParent": trace_parent, + "traceState": trace_state, + "Content-Type": "application/json", + "Accept": "text/event-stream", + }, + body=functions_models.StreamingExecuteEventsQueryRequest( + ontology=ontology, + parameters=parameters, + version=version, + branch=branch, + ), + response_type=functions_models.StreamingExecuteQueryResponse, + request_timeout=request_timeout, + throwable_errors={ + "StreamingExecuteEventsQueryPermissionDenied": functions_errors.StreamingExecuteEventsQueryPermissionDenied, + }, + response_mode=_sdk_internal.get("response_mode", "SSE"), ), ) @@ -1020,7 +1220,8 @@ def execute_async(_: functions_models.ExecuteQueryAsyncResponse): ... def get(_: functions_models.Query): ... def get_by_rid(_: functions_models.Query): ... def get_by_rid_batch(_: functions_models.GetByRidQueriesBatchResponse): ... - def streaming_execute(_: bytes): ... + def streaming_execute(_: functions_models.StreamingExecuteQueryResponse): ... + def streaming_execute_events(_: functions_models.StreamingExecuteQueryResponse): ... self.execute = core.async_with_raw_response(execute, client.execute) self.execute_async = core.async_with_raw_response(execute_async, client.execute_async) @@ -1032,6 +1233,9 @@ def streaming_execute(_: bytes): ... self.streaming_execute = core.async_with_raw_response( streaming_execute, client.streaming_execute ) + self.streaming_execute_events = core.async_with_raw_response( + streaming_execute_events, client.streaming_execute_events + ) class _AsyncQueryClientStreaming: @@ -1041,7 +1245,8 @@ def execute_async(_: functions_models.ExecuteQueryAsyncResponse): ... def get(_: functions_models.Query): ... def get_by_rid(_: functions_models.Query): ... def get_by_rid_batch(_: functions_models.GetByRidQueriesBatchResponse): ... - def streaming_execute(_: bytes): ... + def streaming_execute(_: functions_models.StreamingExecuteQueryResponse): ... + def streaming_execute_events(_: functions_models.StreamingExecuteQueryResponse): ... self.execute = core.async_with_streaming_response(execute, client.execute) self.execute_async = core.async_with_streaming_response(execute_async, client.execute_async) @@ -1050,6 +1255,9 @@ def streaming_execute(_: bytes): ... self.get_by_rid_batch = core.async_with_streaming_response( get_by_rid_batch, client.get_by_rid_batch ) - self.streaming_execute = core.async_with_streaming_response( + self.streaming_execute = core.async_with_sse_response( streaming_execute, client.streaming_execute ) + self.streaming_execute_events = core.async_with_sse_response( + streaming_execute_events, client.streaming_execute_events + ) diff --git a/foundry_sdk/v2/functions/version_id.py b/foundry_sdk/v2/functions/version_id.py index d65fdfbd5..fc4656537 100644 --- a/foundry_sdk/v2/functions/version_id.py +++ b/foundry_sdk/v2/functions/version_id.py @@ -67,7 +67,7 @@ def get( _sdk_internal: core.SdkInternal = {}, ) -> functions_models.VersionId: """ - Gets a specific value type with the given RID. The specified version is returned. + Gets a specific version of a value type with the given RID and version ID. :param value_type_rid: :type value_type_rid: ValueTypeRid @@ -164,7 +164,7 @@ def get( _sdk_internal: core.SdkInternal = {}, ) -> typing.Awaitable[functions_models.VersionId]: """ - Gets a specific value type with the given RID. The specified version is returned. + Gets a specific version of a value type with the given RID and version ID. :param value_type_rid: :type value_type_rid: ValueTypeRid diff --git a/foundry_sdk/v2/ontologies/cipher_text_property.py b/foundry_sdk/v2/ontologies/cipher_text_property.py index c0fb02088..6ac20c9c3 100644 --- a/foundry_sdk/v2/ontologies/cipher_text_property.py +++ b/foundry_sdk/v2/ontologies/cipher_text_property.py @@ -20,6 +20,7 @@ from foundry_sdk import _core as core from foundry_sdk import _errors as errors +from foundry_sdk.v2.core import models as core_models from foundry_sdk.v2.ontologies import models as ontologies_models @@ -62,6 +63,7 @@ def decrypt( primary_key: ontologies_models.PropertyValueEscapedString, property: ontologies_models.PropertyApiName, *, + branch: typing.Optional[core_models.FoundryBranch] = None, request_timeout: typing.Optional[core.Timeout] = None, _sdk_internal: core.SdkInternal = {}, ) -> ontologies_models.DecryptionResult: @@ -76,6 +78,8 @@ def decrypt( :type primary_key: PropertyValueEscapedString :param property: The API name of the CipherText property. To find the API name for your CipherText property, check the **Ontology Manager** or use the **Get object type** endpoint. :type property: PropertyApiName + :param branch: The Foundry branch to read from. If not specified, the default branch will be used. + :type branch: Optional[FoundryBranch] :param request_timeout: timeout setting for this request in seconds. :type request_timeout: Optional[int] :return: Returns the result object. @@ -86,7 +90,9 @@ def decrypt( core.RequestInfo( method="GET", resource_path="/v2/ontologies/{ontology}/objects/{objectType}/{primaryKey}/ciphertexts/{property}/decrypt", - query_params={}, + query_params={ + "branch": branch, + }, path_params={ "ontology": ontology, "objectType": object_type, @@ -158,6 +164,7 @@ def decrypt( primary_key: ontologies_models.PropertyValueEscapedString, property: ontologies_models.PropertyApiName, *, + branch: typing.Optional[core_models.FoundryBranch] = None, request_timeout: typing.Optional[core.Timeout] = None, _sdk_internal: core.SdkInternal = {}, ) -> typing.Awaitable[ontologies_models.DecryptionResult]: @@ -172,6 +179,8 @@ def decrypt( :type primary_key: PropertyValueEscapedString :param property: The API name of the CipherText property. To find the API name for your CipherText property, check the **Ontology Manager** or use the **Get object type** endpoint. :type property: PropertyApiName + :param branch: The Foundry branch to read from. If not specified, the default branch will be used. + :type branch: Optional[FoundryBranch] :param request_timeout: timeout setting for this request in seconds. :type request_timeout: Optional[int] :return: Returns the result object. @@ -182,7 +191,9 @@ def decrypt( core.RequestInfo( method="GET", resource_path="/v2/ontologies/{ontology}/objects/{objectType}/{primaryKey}/ciphertexts/{property}/decrypt", - query_params={}, + query_params={ + "branch": branch, + }, path_params={ "ontology": ontology, "objectType": object_type, diff --git a/foundry_sdk/v2/ontologies/errors.py b/foundry_sdk/v2/ontologies/errors.py index 09c8f6bdb..2c2ca711e 100644 --- a/foundry_sdk/v2/ontologies/errors.py +++ b/foundry_sdk/v2/ontologies/errors.py @@ -190,6 +190,25 @@ class AggregationAccuracyNotSupported(errors.BadRequestError): error_instance_id: str +class AggregationDepthExceededLimitParameters(typing_extensions.TypedDict): + """ + The aggregation request contains too many levels of nested groupings. This can be fixed by reducing the + number of nested groupings in your request. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + depth: int + depthLimit: int + + +@dataclass +class AggregationDepthExceededLimit(errors.BadRequestError): + name: typing.Literal["AggregationDepthExceededLimit"] + parameters: AggregationDepthExceededLimitParameters + error_instance_id: str + + class AggregationGroupCountExceededLimitParameters(typing_extensions.TypedDict): """ The number of groups in the aggregations grouping exceeded the allowed limit. This can typically be fixed by @@ -1496,6 +1515,45 @@ class MarketplaceSdkQueryMappingNotFound(errors.NotFoundError): error_instance_id: str +class MediaUploadDestinationNotConfiguredParameters(typing_extensions.TypedDict): + """ + The media reference property is backed by multiple media set views, and none of them are marked as the upload + destination for this property. Set an upload destination on exactly one of the backing media set views for + this property in Ontology Manager. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + objectType: ontologies_models.ObjectTypeApiName + property: ontologies_models.PropertyApiName + + +@dataclass +class MediaUploadDestinationNotConfigured(errors.BadRequestError): + name: typing.Literal["MediaUploadDestinationNotConfigured"] + parameters: MediaUploadDestinationNotConfiguredParameters + error_instance_id: str + + +class MediaUploadPropertyNotBackedByMediaSetViewParameters(typing_extensions.TypedDict): + """ + The property is not backed by any media set view datasource and cannot accept media uploads. Add a media set + view datasource that includes this property in Ontology Manager. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + objectType: ontologies_models.ObjectTypeApiName + property: ontologies_models.PropertyApiName + + +@dataclass +class MediaUploadPropertyNotBackedByMediaSetView(errors.BadRequestError): + name: typing.Literal["MediaUploadPropertyNotBackedByMediaSetView"] + parameters: MediaUploadPropertyNotBackedByMediaSetViewParameters + error_instance_id: str + + class MissingParameterParameters(typing_extensions.TypedDict): """ Required parameters are missing. Please look at the `parameters` field to see which required parameters are @@ -1545,6 +1603,26 @@ class MultipleGroupByOnFieldNotSupported(errors.BadRequestError): error_instance_id: str +class MultipleMediaUploadDestinationsParameters(typing_extensions.TypedDict): + """ + The media reference property has multiple media set views marked as upload destinations. At most one media + source per property should be configured as the upload destination. This typically indicates an inconsistent + object type configuration; review the backing media sources for this property in Ontology Manager. + """ + + __pydantic_config__ = {"extra": "allow"} # type: ignore + + objectType: ontologies_models.ObjectTypeApiName + property: ontologies_models.PropertyApiName + + +@dataclass +class MultipleMediaUploadDestinations(errors.BadRequestError): + name: typing.Literal["MultipleMediaUploadDestinations"] + parameters: MultipleMediaUploadDestinationsParameters + error_instance_id: str + + class MultiplePropertyValuesNotSupportedParameters(typing_extensions.TypedDict): """ One of the requested property filters does not support multiple values. Please include only a single value for @@ -2518,6 +2596,7 @@ class ViewObjectPermissionDenied(errors.PermissionDeniedError): "ActionTypeNotFound", "ActionValidationFailed", "AggregationAccuracyNotSupported", + "AggregationDepthExceededLimit", "AggregationGroupCountExceededLimit", "AggregationMemoryExceededLimit", "AggregationMetricNotSupported", @@ -2596,9 +2675,12 @@ class ViewObjectPermissionDenied(errors.PermissionDeniedError): "MarketplaceSdkObjectMappingNotFound", "MarketplaceSdkPropertyMappingNotFound", "MarketplaceSdkQueryMappingNotFound", + "MediaUploadDestinationNotConfigured", + "MediaUploadPropertyNotBackedByMediaSetView", "MissingParameter", "MissingValueTypeReference", "MultipleGroupByOnFieldNotSupported", + "MultipleMediaUploadDestinations", "MultiplePropertyValuesNotSupported", "NotCipherFormatted", "ObjectAlreadyExists", diff --git a/foundry_sdk/v2/ontologies/models.py b/foundry_sdk/v2/ontologies/models.py index bb88c00db..160952ac1 100644 --- a/foundry_sdk/v2/ontologies/models.py +++ b/foundry_sdk/v2/ontologies/models.py @@ -3640,6 +3640,11 @@ class ObjectTypeV2(core.ModelBase): rid: ObjectTypeRid title_property: PropertyApiName = pydantic.Field(alias=str("titleProperty")) # type: ignore[literal-required] visibility: typing.Optional[ObjectTypeVisibility] = None + aliases: typing.List[str] + """ + Alternative names (synonyms) for the object type, usable as search terms. This field is only populated on + the get-by-RID read paths (e.g. `getObjectTypeV2`); it is always empty on the `listObjectTypesV2` endpoint. + """ ObjectTypeVisibility: typing_extensions.TypeAlias = typing.Literal["NORMAL", "PROMINENT", "HIDDEN"] diff --git a/foundry_sdk/v2/ontologies/object_type.py b/foundry_sdk/v2/ontologies/object_type.py index 2168b4da9..2e9a0a688 100644 --- a/foundry_sdk/v2/ontologies/object_type.py +++ b/foundry_sdk/v2/ontologies/object_type.py @@ -459,6 +459,9 @@ def list( more results available, at least one result will be present in the response. + Note: the `aliases` field is not populated on this endpoint and will always be empty. To retrieve object type + aliases, use the get-by-RID read paths (e.g. `getObjectTypeV2`). + :param ontology: :type ontology: OntologyIdentifier :param branch: The Foundry branch to list the object types from. If not specified, the default branch will be used. Branches are an experimental feature and not all workflows are supported. @@ -1054,6 +1057,9 @@ def list( more results available, at least one result will be present in the response. + Note: the `aliases` field is not populated on this endpoint and will always be empty. To retrieve object type + aliases, use the get-by-RID read paths (e.g. `getObjectTypeV2`). + :param ontology: :type ontology: OntologyIdentifier :param branch: The Foundry branch to list the object types from. If not specified, the default branch will be used. Branches are an experimental feature and not all workflows are supported. diff --git a/foundry_sdk/v2/ontologies/time_series_value_bank_property.py b/foundry_sdk/v2/ontologies/time_series_value_bank_property.py index 35fb21228..f96e27dba 100644 --- a/foundry_sdk/v2/ontologies/time_series_value_bank_property.py +++ b/foundry_sdk/v2/ontologies/time_series_value_bank_property.py @@ -20,6 +20,7 @@ from foundry_sdk import _core as core from foundry_sdk import _errors as errors +from foundry_sdk.v2.core import models as core_models from foundry_sdk.v2.ontologies import models as ontologies_models @@ -62,6 +63,7 @@ def get_latest_value( primary_key: ontologies_models.PropertyValueEscapedString, property_name: ontologies_models.PropertyApiName, *, + branch: typing.Optional[core_models.FoundryBranch] = None, sdk_package_rid: typing.Optional[ontologies_models.SdkPackageRid] = None, sdk_version: typing.Optional[ontologies_models.SdkVersion] = None, request_timeout: typing.Optional[core.Timeout] = None, @@ -78,6 +80,8 @@ def get_latest_value( :type primary_key: PropertyValueEscapedString :param property_name: The API name of the timeseries property. To find the API name for your property value bank property, check the **Ontology Manager** or use the **Get object type** endpoint. :type property_name: PropertyApiName + :param branch: The Foundry branch to read from. If not specified, the default branch will be used. + :type branch: Optional[FoundryBranch] :param sdk_package_rid: The package rid of the generated SDK. :type sdk_package_rid: Optional[SdkPackageRid] :param sdk_version: The version of the generated SDK. @@ -93,6 +97,7 @@ def get_latest_value( method="GET", resource_path="/v2/ontologies/{ontology}/objects/{objectType}/{primaryKey}/timeseries/{propertyName}/latestValue", query_params={ + "branch": branch, "sdkPackageRid": sdk_package_rid, "sdkVersion": sdk_version, }, @@ -123,6 +128,7 @@ def stream_values( primary_key: ontologies_models.PropertyValueEscapedString, property: ontologies_models.PropertyApiName, *, + branch: typing.Optional[core_models.FoundryBranch] = None, range: typing.Optional[ontologies_models.TimeRange] = None, sdk_package_rid: typing.Optional[ontologies_models.SdkPackageRid] = None, sdk_version: typing.Optional[ontologies_models.SdkVersion] = None, @@ -140,6 +146,8 @@ def stream_values( :type primary_key: PropertyValueEscapedString :param property: The API name of the time series backed property. To find the API name, check the **Ontology Manager** or use the **Get object type** endpoint. :type property: PropertyApiName + :param branch: The Foundry branch to read from. If not specified, the default branch will be used. + :type branch: Optional[FoundryBranch] :param range: :type range: Optional[TimeRange] :param sdk_package_rid: The package rid of the generated SDK. @@ -157,6 +165,7 @@ def stream_values( method="POST", resource_path="/v2/ontologies/{ontology}/objects/{objectType}/{primaryKey}/timeseries/{property}/streamValues", query_params={ + "branch": branch, "sdkPackageRid": sdk_package_rid, "sdkVersion": sdk_version, }, @@ -240,6 +249,7 @@ def get_latest_value( primary_key: ontologies_models.PropertyValueEscapedString, property_name: ontologies_models.PropertyApiName, *, + branch: typing.Optional[core_models.FoundryBranch] = None, sdk_package_rid: typing.Optional[ontologies_models.SdkPackageRid] = None, sdk_version: typing.Optional[ontologies_models.SdkVersion] = None, request_timeout: typing.Optional[core.Timeout] = None, @@ -256,6 +266,8 @@ def get_latest_value( :type primary_key: PropertyValueEscapedString :param property_name: The API name of the timeseries property. To find the API name for your property value bank property, check the **Ontology Manager** or use the **Get object type** endpoint. :type property_name: PropertyApiName + :param branch: The Foundry branch to read from. If not specified, the default branch will be used. + :type branch: Optional[FoundryBranch] :param sdk_package_rid: The package rid of the generated SDK. :type sdk_package_rid: Optional[SdkPackageRid] :param sdk_version: The version of the generated SDK. @@ -271,6 +283,7 @@ def get_latest_value( method="GET", resource_path="/v2/ontologies/{ontology}/objects/{objectType}/{primaryKey}/timeseries/{propertyName}/latestValue", query_params={ + "branch": branch, "sdkPackageRid": sdk_package_rid, "sdkVersion": sdk_version, }, @@ -301,6 +314,7 @@ def stream_values( primary_key: ontologies_models.PropertyValueEscapedString, property: ontologies_models.PropertyApiName, *, + branch: typing.Optional[core_models.FoundryBranch] = None, range: typing.Optional[ontologies_models.TimeRange] = None, sdk_package_rid: typing.Optional[ontologies_models.SdkPackageRid] = None, sdk_version: typing.Optional[ontologies_models.SdkVersion] = None, @@ -318,6 +332,8 @@ def stream_values( :type primary_key: PropertyValueEscapedString :param property: The API name of the time series backed property. To find the API name, check the **Ontology Manager** or use the **Get object type** endpoint. :type property: PropertyApiName + :param branch: The Foundry branch to read from. If not specified, the default branch will be used. + :type branch: Optional[FoundryBranch] :param range: :type range: Optional[TimeRange] :param sdk_package_rid: The package rid of the generated SDK. @@ -335,6 +351,7 @@ def stream_values( method="POST", resource_path="/v2/ontologies/{ontology}/objects/{objectType}/{primaryKey}/timeseries/{property}/streamValues", query_params={ + "branch": branch, "sdkPackageRid": sdk_package_rid, "sdkVersion": sdk_version, }, diff --git a/foundry_sdk/v2/sql_queries/models.py b/foundry_sdk/v2/sql_queries/models.py index 9cbf5c81b..d41be4249 100644 --- a/foundry_sdk/v2/sql_queries/models.py +++ b/foundry_sdk/v2/sql_queries/models.py @@ -87,6 +87,12 @@ class ExecuteOntologySqlQueryRequest(core.ModelBase): dry_run: typing.Optional[bool] = pydantic.Field(alias=str("dryRun"), default=None) # type: ignore[literal-required] """If true, parse and validate the query without executing it. Defaults to false.""" + branch: typing.Optional[core_models.FoundryBranch] = None + """The Foundry branch to execute the query against. If not specified, the default (main) branch is used.""" + + scenario_rid: typing.Optional[ScenarioRid] = pydantic.Field(alias=str("scenarioRid"), default=None) # type: ignore[literal-required] + """The scenario to evaluate the query against. If not specified, no scenario is applied.""" + class ExecuteSqlQueryRequest(core.ModelBase): """ExecuteSqlQueryRequest""" @@ -309,6 +315,10 @@ class RunningQueryStatus(core.ModelBase): type: typing.Literal["running"] = "running" +ScenarioRid: typing_extensions.TypeAlias = core.RID +"""The rid of a scenario to evaluate the query against.""" + + SerializationFormat: typing_extensions.TypeAlias = typing.Literal["ARROW", "CSV"] """Format for SQL query result serialization.""" @@ -407,6 +417,7 @@ class UnnamedParameterValues(core.ModelBase): "Parameters", "QueryStatus", "RunningQueryStatus", + "ScenarioRid", "SerializationFormat", "SqlQueryId", "StructColumnFieldType", diff --git a/foundry_sdk/v2/sql_queries/sql_query.py b/foundry_sdk/v2/sql_queries/sql_query.py index 1110a20bd..b27cebc08 100644 --- a/foundry_sdk/v2/sql_queries/sql_query.py +++ b/foundry_sdk/v2/sql_queries/sql_query.py @@ -184,10 +184,12 @@ def execute_ontology( self, *, query: str, + branch: typing.Optional[core_models.FoundryBranch] = None, dry_run: typing.Optional[bool] = None, parameters: typing.Optional[sql_queries_models.Parameters] = None, preview: typing.Optional[core_models.PreviewMode] = None, row_limit: typing.Optional[int] = None, + scenario_rid: typing.Optional[sql_queries_models.ScenarioRid] = None, request_timeout: typing.Optional[core.Timeout] = None, _sdk_internal: core.SdkInternal = {}, ) -> bytes: @@ -197,6 +199,8 @@ def execute_ontology( :param query: The SQL query to execute. :type query: str + :param branch: The Foundry branch to execute the query against. If not specified, the default (main) branch is used. + :type branch: Optional[FoundryBranch] :param dry_run: If true, parse and validate the query without executing it. Defaults to false. :type dry_run: Optional[bool] :param parameters: Parameters for the SQL query. Can be either unnamed positional parameters or a named parameter mapping. @@ -205,6 +209,8 @@ def execute_ontology( :type preview: Optional[PreviewMode] :param row_limit: Maximum number of rows to return. :type row_limit: Optional[int] + :param scenario_rid: The scenario to evaluate the query against. If not specified, no scenario is applied. + :type scenario_rid: Optional[ScenarioRid] :param request_timeout: timeout setting for this request in seconds. :type request_timeout: Optional[int] :return: Returns the result object. @@ -236,6 +242,8 @@ def execute_ontology( parameters=parameters, row_limit=row_limit, dry_run=dry_run, + branch=branch, + scenario_rid=scenario_rid, ), response_type=bytes, request_timeout=request_timeout, @@ -559,10 +567,12 @@ def execute_ontology( self, *, query: str, + branch: typing.Optional[core_models.FoundryBranch] = None, dry_run: typing.Optional[bool] = None, parameters: typing.Optional[sql_queries_models.Parameters] = None, preview: typing.Optional[core_models.PreviewMode] = None, row_limit: typing.Optional[int] = None, + scenario_rid: typing.Optional[sql_queries_models.ScenarioRid] = None, request_timeout: typing.Optional[core.Timeout] = None, _sdk_internal: core.SdkInternal = {}, ) -> typing.Awaitable[bytes]: @@ -572,6 +582,8 @@ def execute_ontology( :param query: The SQL query to execute. :type query: str + :param branch: The Foundry branch to execute the query against. If not specified, the default (main) branch is used. + :type branch: Optional[FoundryBranch] :param dry_run: If true, parse and validate the query without executing it. Defaults to false. :type dry_run: Optional[bool] :param parameters: Parameters for the SQL query. Can be either unnamed positional parameters or a named parameter mapping. @@ -580,6 +592,8 @@ def execute_ontology( :type preview: Optional[PreviewMode] :param row_limit: Maximum number of rows to return. :type row_limit: Optional[int] + :param scenario_rid: The scenario to evaluate the query against. If not specified, no scenario is applied. + :type scenario_rid: Optional[ScenarioRid] :param request_timeout: timeout setting for this request in seconds. :type request_timeout: Optional[int] :return: Returns the result object. @@ -611,6 +625,8 @@ def execute_ontology( parameters=parameters, row_limit=row_limit, dry_run=dry_run, + branch=branch, + scenario_rid=scenario_rid, ), response_type=bytes, request_timeout=request_timeout, diff --git a/pyproject.toml b/pyproject.toml index 042f6d949..3c8336262 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ annotated-types = ">=0.7.0, <1.0.0" pydantic = ">=2.6.0, <3.0.0" python = "^3.10" httpx = ">=0.25.0, <1.0.0" +httpx-sse = ">=0.4.0, <1.0.0" typing-extensions = ">=4.7.1, <5.0.0" h11 = ">=0.16.0, <1.0.0" # CVE-2025-43859 retrying = "^1.3.7" diff --git a/tests/server.py b/tests/server.py index bffc3bf29..a407b705f 100644 --- a/tests/server.py +++ b/tests/server.py @@ -73,6 +73,53 @@ def generate_data(): return StreamingResponse(generate_data(), media_type="text/plain") +@router.get("/foo/sse") +def foo_sse() -> StreamingResponse: + # A stream of Server-Sent Events whose ``data`` payloads are FooBar JSON objects. Exercises: + # a plain event, a comment/keep-alive line (ignored), and a multi-line ``data:`` event that + # the SSE decoder joins with newlines before JSON parsing. + def generate_data(): + yield 'data: {"foo": "a", "bar": 1}\n\n' + yield ": keep-alive\n\n" + yield 'data: {"foo": "b", "bar": 2}\n\n' + yield 'data: {"foo": "c",\ndata: "bar": 3}\n\n' + + return StreamingResponse(generate_data(), media_type="text/event-stream") + + +@router.get("/foo/sse-keepalive") +def foo_sse_keepalive() -> StreamingResponse: + # Once an event sets ``id:``, the SSE decoder dispatches an empty-data event on every following + # blank line (it never resets last_event_id), and an ``event:``-only terminator does the same. + # Those carry no payload and must be skipped, not decoded. + def generate_data(): + yield 'id: 1\ndata: {"foo": "a", "bar": 1}\n\n' + yield ": keep-alive\n\n" + yield 'data: {"foo": "b", "bar": 2}\n\n' + yield "event: done\n\n" + + return StreamingResponse(generate_data(), media_type="text/event-stream") + + +@router.get("/foo/sse-bad-json") +def foo_sse_bad_json() -> StreamingResponse: + # Event data that is not valid JSON. + return StreamingResponse(iter(["data: {not json}\n\n"]), media_type="text/event-stream") + + +@router.get("/foo/sse-bad-shape") +def foo_sse_bad_shape() -> StreamingResponse: + # Valid JSON whose shape does not match the event type (FooBar requires an int ``bar``). + return StreamingResponse(iter(['data: {"foo": "x"}\n\n']), media_type="text/event-stream") + + +@router.get("/foo/sse-error") +def foo_sse_error(): + # A non-2xx response to an SSE-mode request: the client must fully read the body and raise a + # typed error rather than hand back a broken stream. + raise HTTPException(status_code=404, detail="not found") + + @app.api_route("/proxy/error", methods=["CONNECT"]) def proxy_error(full_path: str): raise HTTPException(status_code=400, detail="Bad Request")