diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 7322f3e71..443f5cdd2 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -55,8 +55,10 @@ BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationResponse, + BlobStorageIntegrationStatusResponse, BlobStorageIntegrationType, BlobStorageIntegrationsResponse, + BlobStorageSyncStatus, CreateBlobStorageIntegrationRequest, ) from .client import AsyncLangfuseAPI, LangfuseAPI @@ -309,8 +311,10 @@ "BlobStorageIntegrationDeletionResponse": ".blob_storage_integrations", "BlobStorageIntegrationFileType": ".blob_storage_integrations", "BlobStorageIntegrationResponse": ".blob_storage_integrations", + "BlobStorageIntegrationStatusResponse": ".blob_storage_integrations", "BlobStorageIntegrationType": ".blob_storage_integrations", "BlobStorageIntegrationsResponse": ".blob_storage_integrations", + "BlobStorageSyncStatus": ".blob_storage_integrations", "BooleanScore": ".commons", "BooleanScoreV1": ".commons", "BulkConfig": ".scim", @@ -594,8 +598,10 @@ def __dir__(): "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", + "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", "BlobStorageIntegrationsResponse", + "BlobStorageSyncStatus", "BooleanScore", "BooleanScoreV1", "BulkConfig", diff --git a/langfuse/api/blob_storage_integrations/__init__.py b/langfuse/api/blob_storage_integrations/__init__.py index abd0d9e84..266be2a6c 100644 --- a/langfuse/api/blob_storage_integrations/__init__.py +++ b/langfuse/api/blob_storage_integrations/__init__.py @@ -12,8 +12,10 @@ BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationResponse, + BlobStorageIntegrationStatusResponse, BlobStorageIntegrationType, BlobStorageIntegrationsResponse, + BlobStorageSyncStatus, CreateBlobStorageIntegrationRequest, ) _dynamic_imports: typing.Dict[str, str] = { @@ -22,8 +24,10 @@ "BlobStorageIntegrationDeletionResponse": ".types", "BlobStorageIntegrationFileType": ".types", "BlobStorageIntegrationResponse": ".types", + "BlobStorageIntegrationStatusResponse": ".types", "BlobStorageIntegrationType": ".types", "BlobStorageIntegrationsResponse": ".types", + "BlobStorageSyncStatus": ".types", "CreateBlobStorageIntegrationRequest": ".types", } @@ -61,7 +65,9 @@ def __dir__(): "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", + "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", "BlobStorageIntegrationsResponse", + "BlobStorageSyncStatus", "CreateBlobStorageIntegrationRequest", ] diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index 6b1f6a677..c79bc82c2 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -16,6 +16,9 @@ ) from .types.blob_storage_integration_file_type import BlobStorageIntegrationFileType from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integration_status_response import ( + BlobStorageIntegrationStatusResponse, +) from .types.blob_storage_integration_type import BlobStorageIntegrationType from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse @@ -192,6 +195,44 @@ def upsert_blob_storage_integration( ) return _response.data + def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationStatusResponse: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationStatusResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.get_blob_storage_integration_status( + id="id", + ) + """ + _response = self._raw_client.get_blob_storage_integration_status( + id, request_options=request_options + ) + return _response.data + def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> BlobStorageIntegrationDeletionResponse: @@ -416,6 +457,52 @@ async def main() -> None: ) return _response.data + async def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationStatusResponse: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationStatusResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.get_blob_storage_integration_status( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_blob_storage_integration_status( + id, request_options=request_options + ) + return _response.data + async def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> BlobStorageIntegrationDeletionResponse: diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 00ee72316..69143f4d6 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -22,6 +22,9 @@ ) from .types.blob_storage_integration_file_type import BlobStorageIntegrationFileType from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integration_status_response import ( + BlobStorageIntegrationStatusResponse, +) from .types.blob_storage_integration_type import BlobStorageIntegrationType from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse @@ -300,6 +303,106 @@ def upsert_blob_storage_integration( body=_response_json, ) + def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[BlobStorageIntegrationStatusResponse]: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[BlobStorageIntegrationStatusResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationStatusResponse, + parse_obj_as( + type_=BlobStorageIntegrationStatusResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[BlobStorageIntegrationDeletionResponse]: @@ -672,6 +775,106 @@ async def upsert_blob_storage_integration( body=_response_json, ) + async def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[BlobStorageIntegrationStatusResponse]: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[BlobStorageIntegrationStatusResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationStatusResponse, + parse_obj_as( + type_=BlobStorageIntegrationStatusResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + async def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[BlobStorageIntegrationDeletionResponse]: diff --git a/langfuse/api/blob_storage_integrations/types/__init__.py b/langfuse/api/blob_storage_integrations/types/__init__.py index cc19f1a6d..e0fe3e9ff 100644 --- a/langfuse/api/blob_storage_integrations/types/__init__.py +++ b/langfuse/api/blob_storage_integrations/types/__init__.py @@ -13,8 +13,12 @@ ) from .blob_storage_integration_file_type import BlobStorageIntegrationFileType from .blob_storage_integration_response import BlobStorageIntegrationResponse + from .blob_storage_integration_status_response import ( + BlobStorageIntegrationStatusResponse, + ) from .blob_storage_integration_type import BlobStorageIntegrationType from .blob_storage_integrations_response import BlobStorageIntegrationsResponse + from .blob_storage_sync_status import BlobStorageSyncStatus from .create_blob_storage_integration_request import ( CreateBlobStorageIntegrationRequest, ) @@ -24,8 +28,10 @@ "BlobStorageIntegrationDeletionResponse": ".blob_storage_integration_deletion_response", "BlobStorageIntegrationFileType": ".blob_storage_integration_file_type", "BlobStorageIntegrationResponse": ".blob_storage_integration_response", + "BlobStorageIntegrationStatusResponse": ".blob_storage_integration_status_response", "BlobStorageIntegrationType": ".blob_storage_integration_type", "BlobStorageIntegrationsResponse": ".blob_storage_integrations_response", + "BlobStorageSyncStatus": ".blob_storage_sync_status", "CreateBlobStorageIntegrationRequest": ".create_blob_storage_integration_request", } @@ -63,7 +69,9 @@ def __dir__(): "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", + "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", "BlobStorageIntegrationsResponse", + "BlobStorageSyncStatus", "CreateBlobStorageIntegrationRequest", ] diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py index 08529ee67..c0536f14e 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py @@ -46,6 +46,12 @@ class BlobStorageIntegrationResponse(UniversalBaseModel): last_sync_at: typing_extensions.Annotated[ typing.Optional[dt.datetime], FieldMetadata(alias="lastSyncAt") ] = None + last_error: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="lastError") + ] = None + last_error_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastErrorAt") + ] = None created_at: typing_extensions.Annotated[ dt.datetime, FieldMetadata(alias="createdAt") ] diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py new file mode 100644 index 000000000..951074990 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .blob_storage_sync_status import BlobStorageSyncStatus + + +class BlobStorageIntegrationStatusResponse(UniversalBaseModel): + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + sync_status: typing_extensions.Annotated[ + BlobStorageSyncStatus, FieldMetadata(alias="syncStatus") + ] + enabled: bool + last_sync_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastSyncAt") + ] = pydantic.Field(default=None) + """ + End of the last successfully exported time window. Compare against your ETL bookmark to determine if new data is available. Null if the integration has never synced. + """ + + next_sync_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="nextSyncAt") + ] = pydantic.Field(default=None) + """ + When the next export is scheduled. Null if no sync has occurred yet. + """ + + last_error: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="lastError") + ] = pydantic.Field(default=None) + """ + Raw error message from the storage provider (S3/Azure/GCS) if the last export failed. Cleared on successful export. + """ + + last_error_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastErrorAt") + ] = pydantic.Field(default=None) + """ + When the last error occurred. Cleared on successful export. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py new file mode 100644 index 000000000..254e06645 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageSyncStatus(enum.StrEnum): + """ + Sync status of the blob storage integration: + - `disabled` — integration is not enabled + - `error` — last export failed (see `lastError` for details) + - `idle` — enabled but has never exported yet + - `queued` — next export is overdue (`nextSyncAt` is in the past) and waiting to be picked up by the worker + - `up_to_date` — all available data has been exported; next export is scheduled for the future + + **ETL usage**: poll this endpoint and check for `up_to_date` status. Compare `lastSyncAt` against your + ETL bookmark to determine if new data is available. Note that exports run with a 30-minute lag buffer, + so `lastSyncAt` will always be at least 30 minutes behind real-time. + """ + + IDLE = "idle" + QUEUED = "queued" + UP_TO_DATE = "up_to_date" + DISABLED = "disabled" + ERROR = "error" + + def visit( + self, + idle: typing.Callable[[], T_Result], + queued: typing.Callable[[], T_Result], + up_to_date: typing.Callable[[], T_Result], + disabled: typing.Callable[[], T_Result], + error: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageSyncStatus.IDLE: + return idle() + if self is BlobStorageSyncStatus.QUEUED: + return queued() + if self is BlobStorageSyncStatus.UP_TO_DATE: + return up_to_date() + if self is BlobStorageSyncStatus.DISABLED: + return disabled() + if self is BlobStorageSyncStatus.ERROR: + return error() diff --git a/langfuse/api/prompts/client.py b/langfuse/api/prompts/client.py index fc6203787..eff5e572f 100644 --- a/langfuse/api/prompts/client.py +++ b/langfuse/api/prompts/client.py @@ -35,6 +35,7 @@ def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Prompt: """ @@ -52,6 +53,9 @@ def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -76,7 +80,11 @@ def get( ) """ _response = self._raw_client.get( - prompt_name, version=version, label=label, request_options=request_options + prompt_name, + version=version, + label=label, + resolve=resolve, + request_options=request_options, ) return _response.data @@ -279,6 +287,7 @@ async def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Prompt: """ @@ -296,6 +305,9 @@ async def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -328,7 +340,11 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.get( - prompt_name, version=version, label=label, request_options=request_options + prompt_name, + version=version, + label=label, + resolve=resolve, + request_options=request_options, ) return _response.data diff --git a/langfuse/api/prompts/raw_client.py b/langfuse/api/prompts/raw_client.py index 81b108968..a2d81a9d1 100644 --- a/langfuse/api/prompts/raw_client.py +++ b/langfuse/api/prompts/raw_client.py @@ -35,6 +35,7 @@ def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Prompt]: """ @@ -52,6 +53,9 @@ def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -65,6 +69,7 @@ def get( params={ "version": version, "label": label, + "resolve": resolve, }, request_options=request_options, ) @@ -511,6 +516,7 @@ async def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Prompt]: """ @@ -528,6 +534,9 @@ async def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -541,6 +550,7 @@ async def get( params={ "version": version, "label": label, + "resolve": resolve, }, request_options=request_options, ) diff --git a/langfuse/api/prompts/types/base_prompt.py b/langfuse/api/prompts/types/base_prompt.py index bd9461600..73cc4a12e 100644 --- a/langfuse/api/prompts/types/base_prompt.py +++ b/langfuse/api/prompts/types/base_prompt.py @@ -34,7 +34,7 @@ class BasePrompt(UniversalBaseModel): FieldMetadata(alias="resolutionGraph"), ] = pydantic.Field(default=None) """ - The dependency resolution graph for the current prompt. Null if prompt has no dependencies. + The dependency resolution graph for the current prompt. Null if the prompt has no dependencies or if `resolve=false` was used. """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(