From ec47eb9f03007a5efa8c194ab98d0aa1377720b9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 14 Mar 2025 05:05:54 +0000 Subject: [PATCH 01/22] chore(internal): codegen related update (#425) --- README.md | 19 ++++++- SECURITY.md | 4 +- src/openlayer/_base_client.py | 97 +---------------------------------- src/openlayer/_client.py | 4 +- 4 files changed, 23 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 395ddd89..8673610d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Openlayer Python library provides convenient access to the Openlayer REST AP application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). -It is generated with [Stainless](https://www.stainlessapi.com/). +It is generated with [Stainless](https://www.stainless.com/). ## Documentation @@ -109,6 +109,23 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Nested params + +Nested parameters are dictionaries, typed using `TypedDict`, for example: + +```python +from openlayer import Openlayer + +client = Openlayer() + +commit = client.projects.commits.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + commit={"message": "Updated the prompt."}, + storage_uri="s3://...", +) +print(commit.commit) +``` + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `openlayer.APIConnectionError` is raised. diff --git a/SECURITY.md b/SECURITY.md index 6dfa13e4..8614b059 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure diff --git a/src/openlayer/_base_client.py b/src/openlayer/_base_client.py index b69cc6b5..171fd21a 100644 --- a/src/openlayer/_base_client.py +++ b/src/openlayer/_base_client.py @@ -9,7 +9,6 @@ import inspect import logging import platform -import warnings import email.utils from types import TracebackType from random import random @@ -36,7 +35,7 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions @@ -51,13 +50,10 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, @@ -331,9 +327,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -346,9 +339,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -356,9 +346,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -784,46 +771,11 @@ def __init__( base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -844,12 +796,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -859,9 +808,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1353,45 +1299,10 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1413,11 +1324,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1427,9 +1335,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: diff --git a/src/openlayer/_client.py b/src/openlayer/_client.py index d5e7a8ea..591e8d6c 100644 --- a/src/openlayer/_client.py +++ b/src/openlayer/_client.py @@ -83,7 +83,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous openlayer client instance. + """Construct a new synchronous Openlayer client instance. This automatically infers the `api_key` argument from the `OPENLAYER_API_KEY` environment variable if it is not provided. """ @@ -266,7 +266,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async openlayer client instance. + """Construct a new async AsyncOpenlayer client instance. This automatically infers the `api_key` argument from the `OPENLAYER_API_KEY` environment variable if it is not provided. """ From db32afe2f233f891bfa1098c29f9ad8f21061ee4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 14 Mar 2025 05:15:51 +0000 Subject: [PATCH 02/22] test: add DEFER_PYDANTIC_BUILD=false flag to tests (#427) --- scripts/test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test b/scripts/test index 4fa5698b..2b878456 100755 --- a/scripts/test +++ b/scripts/test @@ -52,6 +52,8 @@ else echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" From 7111d6d4a8a8524aadbc402ea4761dba2b377170 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 14 Mar 2025 05:44:41 +0000 Subject: [PATCH 03/22] chore(internal): remove extra empty newlines (#428) --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3a4b252a..820d6d73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,6 @@ Homepage = "https://github.com/openlayer-ai/openlayer-python" Repository = "https://github.com/openlayer-ai/openlayer-python" - [tool.rye] managed = true # version pins are in requirements-dev.lock @@ -159,7 +158,6 @@ reportImplicitOverride = true reportImportCycles = false reportPrivateUsage = false - [tool.ruff] line-length = 120 output-format = "grouped" From 10f1de0a71b489ec6e479af5fd8c33bc4f2cc63a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:02:43 +0000 Subject: [PATCH 04/22] feat(api): api update --- .github/workflows/create-releases.yml | 38 +++++++++++++++++++ .github/workflows/publish-pypi.yml | 8 +--- .github/workflows/release-doctor.yml | 1 + LICENSE | 2 +- bin/check-release-environment | 4 ++ .../inference_pipeline_retrieve_response.py | 12 ++++++ .../inference_pipeline_update_response.py | 12 ++++++ .../inference_pipeline_create_params.py | 4 ++ .../inference_pipeline_create_response.py | 12 ++++++ .../inference_pipeline_list_response.py | 12 ++++++ 10 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/create-releases.yml diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml new file mode 100644 index 00000000..b5d9a362 --- /dev/null +++ b/.github/workflows/create-releases.yml @@ -0,0 +1,38 @@ +name: Create releases +on: + schedule: + - cron: '0 5 * * *' # every day at 5am UTC + push: + branches: + - main + +jobs: + release: + name: release + if: github.ref == 'refs/heads/main' && github.repository == 'openlayer-ai/openlayer-python' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: stainless-api/trigger-release-please@v1 + id: release + with: + repo: ${{ github.event.repository.full_name }} + stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} + + - name: Install Rye + if: ${{ steps.release.outputs.releases_created }} + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.35.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Publish to PyPI + if: ${{ steps.release.outputs.releases_created }} + run: | + bash ./bin/publish-pypi + env: + PYPI_TOKEN: ${{ secrets.OPENLAYER_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 60b414a0..5a6c2318 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,13 +1,9 @@ -# This workflow is triggered when a GitHub release is created. -# It can also be run manually to re-publish to PyPI in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/openlayer-ai/openlayer-python/actions/workflows/publish-pypi.yml +# workflow for re-running publishing to PyPI in case it fails for some reason +# you can run this workflow by navigating to https://www.github.com/openlayer-ai/openlayer-python/actions/workflows/publish-pypi.yml name: Publish PyPI on: workflow_dispatch: - release: - types: [published] - jobs: publish: name: publish diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index d6d56f28..95f1a185 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -18,4 +18,5 @@ jobs: run: | bash ./bin/check-release-environment env: + STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} PYPI_TOKEN: ${{ secrets.OPENLAYER_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/LICENSE b/LICENSE index 82530825..ac864c56 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 Openlayer + Copyright 2025 Openlayer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/bin/check-release-environment b/bin/check-release-environment index c0077294..b737e128 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,6 +2,10 @@ errors=() +if [ -z "${STAINLESS_API_KEY}" ]; then + errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") +fi + if [ -z "${PYPI_TOKEN}" ]; then errors+=("The OPENLAYER_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi diff --git a/src/openlayer/types/inference_pipeline_retrieve_response.py b/src/openlayer/types/inference_pipeline_retrieve_response.py index dc157aa7..b6d61869 100644 --- a/src/openlayer/types/inference_pipeline_retrieve_response.py +++ b/src/openlayer/types/inference_pipeline_retrieve_response.py @@ -114,26 +114,37 @@ class WorkspaceMonthlyUsage(BaseModel): class Workspace(BaseModel): id: str + """The workspace id.""" creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The workspace creator id.""" date_created: datetime = FieldInfo(alias="dateCreated") + """The workspace creation date.""" date_updated: datetime = FieldInfo(alias="dateUpdated") + """The workspace last updated date.""" invite_count: int = FieldInfo(alias="inviteCount") + """The number of invites in the workspace.""" member_count: int = FieldInfo(alias="memberCount") + """The number of members in the workspace.""" name: str + """The workspace name.""" period_end_date: Optional[datetime] = FieldInfo(alias="periodEndDate", default=None) + """The end date of the current billing period.""" period_start_date: Optional[datetime] = FieldInfo(alias="periodStartDate", default=None) + """The start date of the current billing period.""" project_count: int = FieldInfo(alias="projectCount") + """The number of projects in the workspace.""" slug: str + """The workspace slug.""" status: Literal[ "active", "past_due", "unpaid", "canceled", "incomplete", "incomplete_expired", "trialing", "paused" @@ -142,6 +153,7 @@ class Workspace(BaseModel): monthly_usage: Optional[List[WorkspaceMonthlyUsage]] = FieldInfo(alias="monthlyUsage", default=None) saml_only_access: Optional[bool] = FieldInfo(alias="samlOnlyAccess", default=None) + """Whether the workspace only allows SAML authentication.""" wildcard_domains: Optional[List[str]] = FieldInfo(alias="wildcardDomains", default=None) diff --git a/src/openlayer/types/inference_pipeline_update_response.py b/src/openlayer/types/inference_pipeline_update_response.py index 1652213f..e8a8638c 100644 --- a/src/openlayer/types/inference_pipeline_update_response.py +++ b/src/openlayer/types/inference_pipeline_update_response.py @@ -114,26 +114,37 @@ class WorkspaceMonthlyUsage(BaseModel): class Workspace(BaseModel): id: str + """The workspace id.""" creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The workspace creator id.""" date_created: datetime = FieldInfo(alias="dateCreated") + """The workspace creation date.""" date_updated: datetime = FieldInfo(alias="dateUpdated") + """The workspace last updated date.""" invite_count: int = FieldInfo(alias="inviteCount") + """The number of invites in the workspace.""" member_count: int = FieldInfo(alias="memberCount") + """The number of members in the workspace.""" name: str + """The workspace name.""" period_end_date: Optional[datetime] = FieldInfo(alias="periodEndDate", default=None) + """The end date of the current billing period.""" period_start_date: Optional[datetime] = FieldInfo(alias="periodStartDate", default=None) + """The start date of the current billing period.""" project_count: int = FieldInfo(alias="projectCount") + """The number of projects in the workspace.""" slug: str + """The workspace slug.""" status: Literal[ "active", "past_due", "unpaid", "canceled", "incomplete", "incomplete_expired", "trialing", "paused" @@ -142,6 +153,7 @@ class Workspace(BaseModel): monthly_usage: Optional[List[WorkspaceMonthlyUsage]] = FieldInfo(alias="monthlyUsage", default=None) saml_only_access: Optional[bool] = FieldInfo(alias="samlOnlyAccess", default=None) + """Whether the workspace only allows SAML authentication.""" wildcard_domains: Optional[List[str]] = FieldInfo(alias="wildcardDomains", default=None) diff --git a/src/openlayer/types/projects/inference_pipeline_create_params.py b/src/openlayer/types/projects/inference_pipeline_create_params.py index eb5c467e..a13f2057 100644 --- a/src/openlayer/types/projects/inference_pipeline_create_params.py +++ b/src/openlayer/types/projects/inference_pipeline_create_params.py @@ -40,11 +40,15 @@ class Project(TypedDict, total=False): class Workspace(TypedDict, total=False): name: Required[str] + """The workspace name.""" slug: Required[str] + """The workspace slug.""" invite_code: Annotated[str, PropertyInfo(alias="inviteCode")] + """The workspace invite code.""" saml_only_access: Annotated[bool, PropertyInfo(alias="samlOnlyAccess")] + """Whether the workspace only allows SAML authentication.""" wildcard_domains: Annotated[List[str], PropertyInfo(alias="wildcardDomains")] diff --git a/src/openlayer/types/projects/inference_pipeline_create_response.py b/src/openlayer/types/projects/inference_pipeline_create_response.py index 26ee50db..a6085579 100644 --- a/src/openlayer/types/projects/inference_pipeline_create_response.py +++ b/src/openlayer/types/projects/inference_pipeline_create_response.py @@ -114,26 +114,37 @@ class WorkspaceMonthlyUsage(BaseModel): class Workspace(BaseModel): id: str + """The workspace id.""" creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The workspace creator id.""" date_created: datetime = FieldInfo(alias="dateCreated") + """The workspace creation date.""" date_updated: datetime = FieldInfo(alias="dateUpdated") + """The workspace last updated date.""" invite_count: int = FieldInfo(alias="inviteCount") + """The number of invites in the workspace.""" member_count: int = FieldInfo(alias="memberCount") + """The number of members in the workspace.""" name: str + """The workspace name.""" period_end_date: Optional[datetime] = FieldInfo(alias="periodEndDate", default=None) + """The end date of the current billing period.""" period_start_date: Optional[datetime] = FieldInfo(alias="periodStartDate", default=None) + """The start date of the current billing period.""" project_count: int = FieldInfo(alias="projectCount") + """The number of projects in the workspace.""" slug: str + """The workspace slug.""" status: Literal[ "active", "past_due", "unpaid", "canceled", "incomplete", "incomplete_expired", "trialing", "paused" @@ -142,6 +153,7 @@ class Workspace(BaseModel): monthly_usage: Optional[List[WorkspaceMonthlyUsage]] = FieldInfo(alias="monthlyUsage", default=None) saml_only_access: Optional[bool] = FieldInfo(alias="samlOnlyAccess", default=None) + """Whether the workspace only allows SAML authentication.""" wildcard_domains: Optional[List[str]] = FieldInfo(alias="wildcardDomains", default=None) diff --git a/src/openlayer/types/projects/inference_pipeline_list_response.py b/src/openlayer/types/projects/inference_pipeline_list_response.py index 45bd105d..0d5be4eb 100644 --- a/src/openlayer/types/projects/inference_pipeline_list_response.py +++ b/src/openlayer/types/projects/inference_pipeline_list_response.py @@ -115,26 +115,37 @@ class ItemWorkspaceMonthlyUsage(BaseModel): class ItemWorkspace(BaseModel): id: str + """The workspace id.""" creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The workspace creator id.""" date_created: datetime = FieldInfo(alias="dateCreated") + """The workspace creation date.""" date_updated: datetime = FieldInfo(alias="dateUpdated") + """The workspace last updated date.""" invite_count: int = FieldInfo(alias="inviteCount") + """The number of invites in the workspace.""" member_count: int = FieldInfo(alias="memberCount") + """The number of members in the workspace.""" name: str + """The workspace name.""" period_end_date: Optional[datetime] = FieldInfo(alias="periodEndDate", default=None) + """The end date of the current billing period.""" period_start_date: Optional[datetime] = FieldInfo(alias="periodStartDate", default=None) + """The start date of the current billing period.""" project_count: int = FieldInfo(alias="projectCount") + """The number of projects in the workspace.""" slug: str + """The workspace slug.""" status: Literal[ "active", "past_due", "unpaid", "canceled", "incomplete", "incomplete_expired", "trialing", "paused" @@ -143,6 +154,7 @@ class ItemWorkspace(BaseModel): monthly_usage: Optional[List[ItemWorkspaceMonthlyUsage]] = FieldInfo(alias="monthlyUsage", default=None) saml_only_access: Optional[bool] = FieldInfo(alias="samlOnlyAccess", default=None) + """Whether the workspace only allows SAML authentication.""" wildcard_domains: Optional[List[str]] = FieldInfo(alias="wildcardDomains", default=None) From c87c92ded5591542b9c939c775fa2d09fb0885c5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 05:12:14 +0000 Subject: [PATCH 05/22] chore(internal): codegen related update --- README.md | 7 +- mypy.ini | 2 +- pyproject.toml | 3 +- requirements-dev.lock | 9 +- requirements.lock | 3 +- src/openlayer/_base_client.py | 6 + src/openlayer/_models.py | 14 ++- src/openlayer/_response.py | 12 +- src/openlayer/resources/commits/commits.py | 4 +- .../resources/commits/test_results.py | 4 +- .../resources/inference_pipelines/data.py | 4 +- .../inference_pipelines.py | 4 +- .../resources/inference_pipelines/rows.py | 4 +- .../inference_pipelines/test_results.py | 4 +- src/openlayer/resources/projects/commits.py | 4 +- .../resources/projects/inference_pipelines.py | 4 +- src/openlayer/resources/projects/projects.py | 4 +- .../resources/storage/presigned_url.py | 4 +- src/openlayer/resources/storage/storage.py | 4 +- tests/api_resources/projects/test_commits.py | 108 ++++++++++++++++-- .../projects/test_inference_pipelines.py | 76 ++++++++++++ tests/test_client.py | 25 ++-- tests/test_models.py | 10 ++ 23 files changed, 259 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 8673610d..99cee3f6 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ except openlayer.APIStatusError as e: print(e.response) ``` -Error codes are as followed: +Error codes are as follows: | Status Code | Error Type | | ----------- | -------------------------- | @@ -373,8 +373,7 @@ If you need to access undocumented endpoints, params, or response properties, th #### Undocumented endpoints To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other -http verbs. Options on the client will be respected (such as retries) will be respected when making this -request. +http verbs. Options on the client will be respected (such as retries) when making this request. ```py import httpx @@ -446,7 +445,7 @@ with Openlayer() as client: This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 3. Changes that we do not expect to impact the vast majority of users in practice. We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. diff --git a/mypy.ini b/mypy.ini index 0ef49b86..7d5e61da 100644 --- a/mypy.ini +++ b/mypy.ini @@ -41,7 +41,7 @@ cache_fine_grained = True # ``` # Changing this codegen to make mypy happy would increase complexity # and would not be worth it. -disable_error_code = func-returns-value +disable_error_code = func-returns-value,overload-cannot-match # https://github.com/python/mypy/issues/12162 [mypy.overrides] diff --git a/pyproject.toml b/pyproject.toml index 820d6d73..f166fcb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0" + "nest_asyncio==1.6.0", ] [tool.rye.scripts] @@ -134,6 +134,7 @@ testpaths = ["tests"] addopts = "--tb=short" xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" filterwarnings = [ "error" ] diff --git a/requirements-dev.lock b/requirements-dev.lock index 257368a1..dcd49711 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -38,7 +38,7 @@ h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx -httpx==0.25.2 +httpx==0.28.1 # via openlayer # via respx idna==3.4 @@ -52,7 +52,7 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.13.0 +mypy==1.14.1 mypy-extensions==1.0.0 # via mypy nest-asyncio==1.6.0 @@ -80,7 +80,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.390 +pyright==1.1.392.post0 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 @@ -96,7 +96,7 @@ requests==2.32.3 # via requests-toolbelt requests-toolbelt==1.0.0 # via openlayer -respx==0.20.2 +respx==0.22.0 rich==13.7.1 ruff==0.6.9 setuptools==68.2.2 @@ -105,7 +105,6 @@ six==1.16.0 # via python-dateutil sniffio==1.3.0 # via anyio - # via httpx # via openlayer time-machine==2.9.0 tomli==2.0.2 diff --git a/requirements.lock b/requirements.lock index 14bdfd3f..96517994 100644 --- a/requirements.lock +++ b/requirements.lock @@ -28,7 +28,7 @@ h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx -httpx==0.25.2 +httpx==0.28.1 # via openlayer idna==3.4 # via anyio @@ -60,7 +60,6 @@ six==1.16.0 # via python-dateutil sniffio==1.3.0 # via anyio - # via httpx # via openlayer tqdm==4.67.1 # via openlayer diff --git a/src/openlayer/_base_client.py b/src/openlayer/_base_client.py index 171fd21a..0467e487 100644 --- a/src/openlayer/_base_client.py +++ b/src/openlayer/_base_client.py @@ -754,6 +754,9 @@ def __init__(self, **kwargs: Any) -> None: class SyncHttpxClientWrapper(DefaultHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: self.close() except Exception: @@ -1280,6 +1283,9 @@ def __init__(self, **kwargs: Any) -> None: class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: # TODO(someday): support non asyncio runtimes here asyncio.get_running_loop().create_task(self.aclose()) diff --git a/src/openlayer/_models.py b/src/openlayer/_models.py index 7a547ce5..9a918aab 100644 --- a/src/openlayer/_models.py +++ b/src/openlayer/_models.py @@ -179,14 +179,14 @@ def __str__(self) -> str: @classmethod @override def construct( # pyright: ignore[reportIncompatibleMethodOverride] - cls: Type[ModelT], + __cls: Type[ModelT], _fields_set: set[str] | None = None, **values: object, ) -> ModelT: - m = cls.__new__(cls) + m = __cls.__new__(__cls) fields_values: dict[str, object] = {} - config = get_model_config(cls) + config = get_model_config(__cls) populate_by_name = ( config.allow_population_by_field_name if isinstance(config, _ConfigProtocol) @@ -196,7 +196,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] if _fields_set is None: _fields_set = set() - model_fields = get_model_fields(cls) + model_fields = get_model_fields(__cls) for name, field in model_fields.items(): key = field.alias if key is None or (key not in values and populate_by_name): @@ -488,7 +488,11 @@ def construct_type(*, value: object, type_: object) -> object: _, items_type = get_args(type_) # Dict[_, items_type] return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} - if not is_literal_type(type_) and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)): + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): if is_list(value): return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] diff --git a/src/openlayer/_response.py b/src/openlayer/_response.py index c7cc89ef..36b9e9d3 100644 --- a/src/openlayer/_response.py +++ b/src/openlayer/_response.py @@ -136,6 +136,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to and is_annotated_type(cast_to): cast_to = extract_type_arg(cast_to, 0) + origin = get_origin(cast_to) or cast_to + if self._is_sse_stream: if to: if not is_stream_class_type(to): @@ -195,8 +197,6 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == bool: return cast(R, response.text.lower() == "true") - origin = get_origin(cast_to) or cast_to - if origin == APIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") @@ -210,7 +210,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from openlayer import BaseModel`") if ( diff --git a/src/openlayer/resources/commits/commits.py b/src/openlayer/resources/commits/commits.py index 3e64e524..64ae8377 100644 --- a/src/openlayer/resources/commits/commits.py +++ b/src/openlayer/resources/commits/commits.py @@ -35,7 +35,7 @@ def test_results(self) -> TestResultsResource: @cached_property def with_raw_response(self) -> CommitsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -93,7 +93,7 @@ def test_results(self) -> AsyncTestResultsResource: @cached_property def with_raw_response(self) -> AsyncCommitsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/commits/test_results.py b/src/openlayer/resources/commits/test_results.py index 0d37c7e0..53e5d18f 100644 --- a/src/openlayer/resources/commits/test_results.py +++ b/src/openlayer/resources/commits/test_results.py @@ -32,7 +32,7 @@ class TestResultsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> TestResultsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -116,7 +116,7 @@ class AsyncTestResultsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTestResultsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/inference_pipelines/data.py b/src/openlayer/resources/inference_pipelines/data.py index f8b4b547..3d72abab 100644 --- a/src/openlayer/resources/inference_pipelines/data.py +++ b/src/openlayer/resources/inference_pipelines/data.py @@ -30,7 +30,7 @@ class DataResource(SyncAPIResource): @cached_property def with_raw_response(self) -> DataResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -100,7 +100,7 @@ class AsyncDataResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncDataResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/inference_pipelines/inference_pipelines.py b/src/openlayer/resources/inference_pipelines/inference_pipelines.py index 60ce3fcc..fa993789 100644 --- a/src/openlayer/resources/inference_pipelines/inference_pipelines.py +++ b/src/openlayer/resources/inference_pipelines/inference_pipelines.py @@ -68,7 +68,7 @@ def test_results(self) -> TestResultsResource: @cached_property def with_raw_response(self) -> InferencePipelinesResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -234,7 +234,7 @@ def test_results(self) -> AsyncTestResultsResource: @cached_property def with_raw_response(self) -> AsyncInferencePipelinesResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/inference_pipelines/rows.py b/src/openlayer/resources/inference_pipelines/rows.py index f763b1ab..ad1f1fe3 100644 --- a/src/openlayer/resources/inference_pipelines/rows.py +++ b/src/openlayer/resources/inference_pipelines/rows.py @@ -30,7 +30,7 @@ class RowsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> RowsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -102,7 +102,7 @@ class AsyncRowsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncRowsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/inference_pipelines/test_results.py b/src/openlayer/resources/inference_pipelines/test_results.py index 4bcb435e..c1eaae19 100644 --- a/src/openlayer/resources/inference_pipelines/test_results.py +++ b/src/openlayer/resources/inference_pipelines/test_results.py @@ -32,7 +32,7 @@ class TestResultsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> TestResultsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -114,7 +114,7 @@ class AsyncTestResultsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTestResultsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/projects/commits.py b/src/openlayer/resources/projects/commits.py index 9bba5fb8..af8b4292 100644 --- a/src/openlayer/resources/projects/commits.py +++ b/src/openlayer/resources/projects/commits.py @@ -31,7 +31,7 @@ class CommitsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> CommitsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -155,7 +155,7 @@ class AsyncCommitsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncCommitsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/projects/inference_pipelines.py b/src/openlayer/resources/projects/inference_pipelines.py index 0ae5de1a..f6161775 100644 --- a/src/openlayer/resources/projects/inference_pipelines.py +++ b/src/openlayer/resources/projects/inference_pipelines.py @@ -31,7 +31,7 @@ class InferencePipelinesResource(SyncAPIResource): @cached_property def with_raw_response(self) -> InferencePipelinesResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -155,7 +155,7 @@ class AsyncInferencePipelinesResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncInferencePipelinesResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/projects/projects.py b/src/openlayer/resources/projects/projects.py index e5e90392..de402a7c 100644 --- a/src/openlayer/resources/projects/projects.py +++ b/src/openlayer/resources/projects/projects.py @@ -56,7 +56,7 @@ def inference_pipelines(self) -> InferencePipelinesResource: @cached_property def with_raw_response(self) -> ProjectsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -187,7 +187,7 @@ def inference_pipelines(self) -> AsyncInferencePipelinesResource: @cached_property def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/storage/presigned_url.py b/src/openlayer/resources/storage/presigned_url.py index 5fb6fa1c..c875a551 100644 --- a/src/openlayer/resources/storage/presigned_url.py +++ b/src/openlayer/resources/storage/presigned_url.py @@ -28,7 +28,7 @@ class PresignedURLResource(SyncAPIResource): @cached_property def with_raw_response(self) -> PresignedURLResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -88,7 +88,7 @@ class AsyncPresignedURLResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncPresignedURLResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/src/openlayer/resources/storage/storage.py b/src/openlayer/resources/storage/storage.py index ea2a3c99..307335a8 100644 --- a/src/openlayer/resources/storage/storage.py +++ b/src/openlayer/resources/storage/storage.py @@ -24,7 +24,7 @@ def presigned_url(self) -> PresignedURLResource: @cached_property def with_raw_response(self) -> StorageResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers @@ -49,7 +49,7 @@ def presigned_url(self) -> AsyncPresignedURLResource: @cached_property def with_raw_response(self) -> AsyncStorageResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers diff --git a/tests/api_resources/projects/test_commits.py b/tests/api_resources/projects/test_commits.py index 62fc86ca..c36a16ee 100644 --- a/tests/api_resources/projects/test_commits.py +++ b/tests/api_resources/projects/test_commits.py @@ -21,7 +21,15 @@ class TestCommits: def test_method_create(self, client: Openlayer) -> None: commit = client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) assert_matches_type(CommitCreateResponse, commit, path=["response"]) @@ -30,7 +38,19 @@ def test_method_create(self, client: Openlayer) -> None: def test_method_create_with_all_params(self, client: Openlayer) -> None: commit = client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "date_created": "2024-03-22T11:31:01.185Z", + "git_commit_ref": "main", + "git_commit_sha": 0, + "git_commit_url": "gitCommitUrl", + }, storage_uri="s3://...", archived=False, deployment_status="Deployed", @@ -41,7 +61,15 @@ def test_method_create_with_all_params(self, client: Openlayer) -> None: def test_raw_response_create(self, client: Openlayer) -> None: response = client.projects.commits.with_raw_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) @@ -54,7 +82,15 @@ def test_raw_response_create(self, client: Openlayer) -> None: def test_streaming_response_create(self, client: Openlayer) -> None: with client.projects.commits.with_streaming_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) as response: assert not response.is_closed @@ -70,7 +106,15 @@ def test_path_params_create(self, client: Openlayer) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): client.projects.commits.with_raw_response.create( project_id="", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) @@ -129,7 +173,15 @@ class TestAsyncCommits: async def test_method_create(self, async_client: AsyncOpenlayer) -> None: commit = await async_client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) assert_matches_type(CommitCreateResponse, commit, path=["response"]) @@ -138,7 +190,19 @@ async def test_method_create(self, async_client: AsyncOpenlayer) -> None: async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) -> None: commit = await async_client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "date_created": "2024-03-22T11:31:01.185Z", + "git_commit_ref": "main", + "git_commit_sha": 0, + "git_commit_url": "gitCommitUrl", + }, storage_uri="s3://...", archived=False, deployment_status="Deployed", @@ -149,7 +213,15 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) async def test_raw_response_create(self, async_client: AsyncOpenlayer) -> None: response = await async_client.projects.commits.with_raw_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) @@ -162,7 +234,15 @@ async def test_raw_response_create(self, async_client: AsyncOpenlayer) -> None: async def test_streaming_response_create(self, async_client: AsyncOpenlayer) -> None: async with async_client.projects.commits.with_streaming_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) as response: assert not response.is_closed @@ -178,7 +258,15 @@ async def test_path_params_create(self, async_client: AsyncOpenlayer) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): await async_client.projects.commits.with_raw_response.create( project_id="", - commit={"message": "Updated the prompt."}, + commit={ + "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "file_size": 1024, + "message": "Updated the prompt.", + "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "storage_uri": "s3://...", + "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, storage_uri="s3://...", ) diff --git a/tests/api_resources/projects/test_inference_pipelines.py b/tests/api_resources/projects/test_inference_pipelines.py index ea0bb5b6..71f74bf7 100644 --- a/tests/api_resources/projects/test_inference_pipelines.py +++ b/tests/api_resources/projects/test_inference_pipelines.py @@ -36,14 +36,52 @@ def test_method_create_with_all_params(self, client: Openlayer) -> None: description="This pipeline is used for production.", name="production", project={ + "creator_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "date_created": "2024-03-22T11:31:01.185Z", + "date_updated": "2024-03-22T11:31:01.185Z", + "development_goal_count": 5, + "goal_count": 10, + "inference_pipeline_count": 1, + "monitoring_goal_count": 5, "name": "My Project", "task_type": "llm-base", + "version_count": 2, + "workspace_id": "055fddb1-261f-4654-8598-f6347ee46a09", "description": "My project description.", + "git_repo": { + "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "date_connected": "2019-12-27T18:11:19.117Z", + "date_updated": "2019-12-27T18:11:19.117Z", + "git_account_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "git_id": 0, + "name": "name", + "private": True, + "project_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "slug": "slug", + "url": "url", + "branch": "branch", + "root_dir": "rootDir", + }, }, workspace={ + "creator_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "date_created": "2019-12-27T18:11:19.117Z", + "date_updated": "2019-12-27T18:11:19.117Z", + "invite_count": 0, + "member_count": 0, "name": "Openlayer", + "period_end_date": "2019-12-27T18:11:19.117Z", + "period_start_date": "2019-12-27T18:11:19.117Z", + "project_count": 0, "slug": "openlayer", "invite_code": "inviteCode", + "monthly_usage": [ + { + "execution_time_ms": 0, + "month_year": "2019-12-27", + "prediction_count": 0, + } + ], "saml_only_access": True, "wildcard_domains": ["string"], }, @@ -155,14 +193,52 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) description="This pipeline is used for production.", name="production", project={ + "creator_id": "589ece63-49a2-41b4-98e1-10547761d4b0", + "date_created": "2024-03-22T11:31:01.185Z", + "date_updated": "2024-03-22T11:31:01.185Z", + "development_goal_count": 5, + "goal_count": 10, + "inference_pipeline_count": 1, + "monitoring_goal_count": 5, "name": "My Project", "task_type": "llm-base", + "version_count": 2, + "workspace_id": "055fddb1-261f-4654-8598-f6347ee46a09", "description": "My project description.", + "git_repo": { + "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "date_connected": "2019-12-27T18:11:19.117Z", + "date_updated": "2019-12-27T18:11:19.117Z", + "git_account_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "git_id": 0, + "name": "name", + "private": True, + "project_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "slug": "slug", + "url": "url", + "branch": "branch", + "root_dir": "rootDir", + }, }, workspace={ + "creator_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "date_created": "2019-12-27T18:11:19.117Z", + "date_updated": "2019-12-27T18:11:19.117Z", + "invite_count": 0, + "member_count": 0, "name": "Openlayer", + "period_end_date": "2019-12-27T18:11:19.117Z", + "period_start_date": "2019-12-27T18:11:19.117Z", + "project_count": 0, "slug": "openlayer", "invite_code": "inviteCode", + "monthly_usage": [ + { + "execution_time_ms": 0, + "month_year": "2019-12-27", + "prediction_count": 0, + } + ], "saml_only_access": True, "wildcard_domains": ["string"], }, diff --git a/tests/test_client.py b/tests/test_client.py index 64a81986..089f0652 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,6 +6,7 @@ import os import sys import json +import time import asyncio import inspect import subprocess @@ -1815,10 +1816,20 @@ async def test_main() -> None: [sys.executable, "-c", test_code], text=True, ) as process: - try: - process.wait(2) - if process.returncode: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - except subprocess.TimeoutExpired as e: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e + timeout = 10 # seconds + + start_time = time.monotonic() + while True: + return_code = process.poll() + if return_code is not None: + if return_code != 0: + raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") + + # success + break + + if time.monotonic() - start_time > timeout: + process.kill() + raise AssertionError("calling get_platform using asyncify resulted in a hung process") + + time.sleep(0.1) diff --git a/tests/test_models.py b/tests/test_models.py index 91d9ec71..5108c6a7 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -844,3 +844,13 @@ class Model(BaseModel): assert m.alias == "foo" assert isinstance(m.union, str) assert m.union == "bar" + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +def test_field_named_cls() -> None: + class Model(BaseModel): + cls: str + + m = construct_type(value={"cls": "foo"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.cls, str) From dfd7861657bbd5f761649b5f956cb9c85e9bd1e4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 05:30:24 +0000 Subject: [PATCH 06/22] chore(internal): codegen related update --- .github/workflows/ci.yml | 3 - scripts/bootstrap | 2 +- scripts/lint | 1 - .../inference_pipelines/test_rows.py | 2 - tests/api_resources/projects/test_commits.py | 108 ++---------------- .../projects/test_inference_pipelines.py | 78 ------------- 6 files changed, 11 insertions(+), 183 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 565ec95e..e503784c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ jobs: lint: name: lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -30,5 +29,3 @@ jobs: - name: Run lints run: ./scripts/lint - - diff --git a/scripts/bootstrap b/scripts/bootstrap index 8c5c60eb..e84fe62c 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then brew bundle check >/dev/null 2>&1 || { echo "==> Installing Homebrew dependencies…" brew bundle diff --git a/scripts/lint b/scripts/lint index 763eb089..174dd16b 100755 --- a/scripts/lint +++ b/scripts/lint @@ -9,4 +9,3 @@ rye run lint echo "==> Making sure it imports" rye run python -c 'import openlayer' - diff --git a/tests/api_resources/inference_pipelines/test_rows.py b/tests/api_resources/inference_pipelines/test_rows.py index bef1c42f..d9694072 100644 --- a/tests/api_resources/inference_pipelines/test_rows.py +++ b/tests/api_resources/inference_pipelines/test_rows.py @@ -35,7 +35,6 @@ def test_method_update_with_all_params(self, client: Openlayer) -> None: config={ "ground_truth_column_name": "ground_truth", "human_feedback_column_name": "human_feedback", - "inference_id_column_name": "id", "latency_column_name": "latency", "timestamp_column_name": "timestamp", }, @@ -101,7 +100,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpenlayer) config={ "ground_truth_column_name": "ground_truth", "human_feedback_column_name": "human_feedback", - "inference_id_column_name": "id", "latency_column_name": "latency", "timestamp_column_name": "timestamp", }, diff --git a/tests/api_resources/projects/test_commits.py b/tests/api_resources/projects/test_commits.py index c36a16ee..62fc86ca 100644 --- a/tests/api_resources/projects/test_commits.py +++ b/tests/api_resources/projects/test_commits.py @@ -21,15 +21,7 @@ class TestCommits: def test_method_create(self, client: Openlayer) -> None: commit = client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) assert_matches_type(CommitCreateResponse, commit, path=["response"]) @@ -38,19 +30,7 @@ def test_method_create(self, client: Openlayer) -> None: def test_method_create_with_all_params(self, client: Openlayer) -> None: commit = client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "date_created": "2024-03-22T11:31:01.185Z", - "git_commit_ref": "main", - "git_commit_sha": 0, - "git_commit_url": "gitCommitUrl", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", archived=False, deployment_status="Deployed", @@ -61,15 +41,7 @@ def test_method_create_with_all_params(self, client: Openlayer) -> None: def test_raw_response_create(self, client: Openlayer) -> None: response = client.projects.commits.with_raw_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) @@ -82,15 +54,7 @@ def test_raw_response_create(self, client: Openlayer) -> None: def test_streaming_response_create(self, client: Openlayer) -> None: with client.projects.commits.with_streaming_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) as response: assert not response.is_closed @@ -106,15 +70,7 @@ def test_path_params_create(self, client: Openlayer) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): client.projects.commits.with_raw_response.create( project_id="", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) @@ -173,15 +129,7 @@ class TestAsyncCommits: async def test_method_create(self, async_client: AsyncOpenlayer) -> None: commit = await async_client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) assert_matches_type(CommitCreateResponse, commit, path=["response"]) @@ -190,19 +138,7 @@ async def test_method_create(self, async_client: AsyncOpenlayer) -> None: async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) -> None: commit = await async_client.projects.commits.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "date_created": "2024-03-22T11:31:01.185Z", - "git_commit_ref": "main", - "git_commit_sha": 0, - "git_commit_url": "gitCommitUrl", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", archived=False, deployment_status="Deployed", @@ -213,15 +149,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) async def test_raw_response_create(self, async_client: AsyncOpenlayer) -> None: response = await async_client.projects.commits.with_raw_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) @@ -234,15 +162,7 @@ async def test_raw_response_create(self, async_client: AsyncOpenlayer) -> None: async def test_streaming_response_create(self, async_client: AsyncOpenlayer) -> None: async with async_client.projects.commits.with_streaming_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) as response: assert not response.is_closed @@ -258,15 +178,7 @@ async def test_path_params_create(self, async_client: AsyncOpenlayer) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): await async_client.projects.commits.with_raw_response.create( project_id="", - commit={ - "author_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "file_size": 1024, - "message": "Updated the prompt.", - "ml_model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "storage_uri": "s3://...", - "training_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "validation_dataset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - }, + commit={"message": "Updated the prompt."}, storage_uri="s3://...", ) diff --git a/tests/api_resources/projects/test_inference_pipelines.py b/tests/api_resources/projects/test_inference_pipelines.py index 71f74bf7..5983c59d 100644 --- a/tests/api_resources/projects/test_inference_pipelines.py +++ b/tests/api_resources/projects/test_inference_pipelines.py @@ -36,52 +36,13 @@ def test_method_create_with_all_params(self, client: Openlayer) -> None: description="This pipeline is used for production.", name="production", project={ - "creator_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "date_created": "2024-03-22T11:31:01.185Z", - "date_updated": "2024-03-22T11:31:01.185Z", - "development_goal_count": 5, - "goal_count": 10, - "inference_pipeline_count": 1, - "monitoring_goal_count": 5, "name": "My Project", "task_type": "llm-base", - "version_count": 2, - "workspace_id": "055fddb1-261f-4654-8598-f6347ee46a09", "description": "My project description.", - "git_repo": { - "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "date_connected": "2019-12-27T18:11:19.117Z", - "date_updated": "2019-12-27T18:11:19.117Z", - "git_account_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "git_id": 0, - "name": "name", - "private": True, - "project_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "slug": "slug", - "url": "url", - "branch": "branch", - "root_dir": "rootDir", - }, }, workspace={ - "creator_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "date_created": "2019-12-27T18:11:19.117Z", - "date_updated": "2019-12-27T18:11:19.117Z", - "invite_count": 0, - "member_count": 0, "name": "Openlayer", - "period_end_date": "2019-12-27T18:11:19.117Z", - "period_start_date": "2019-12-27T18:11:19.117Z", - "project_count": 0, "slug": "openlayer", - "invite_code": "inviteCode", - "monthly_usage": [ - { - "execution_time_ms": 0, - "month_year": "2019-12-27", - "prediction_count": 0, - } - ], "saml_only_access": True, "wildcard_domains": ["string"], }, @@ -193,52 +154,13 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) description="This pipeline is used for production.", name="production", project={ - "creator_id": "589ece63-49a2-41b4-98e1-10547761d4b0", - "date_created": "2024-03-22T11:31:01.185Z", - "date_updated": "2024-03-22T11:31:01.185Z", - "development_goal_count": 5, - "goal_count": 10, - "inference_pipeline_count": 1, - "monitoring_goal_count": 5, "name": "My Project", "task_type": "llm-base", - "version_count": 2, - "workspace_id": "055fddb1-261f-4654-8598-f6347ee46a09", "description": "My project description.", - "git_repo": { - "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "date_connected": "2019-12-27T18:11:19.117Z", - "date_updated": "2019-12-27T18:11:19.117Z", - "git_account_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "git_id": 0, - "name": "name", - "private": True, - "project_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "slug": "slug", - "url": "url", - "branch": "branch", - "root_dir": "rootDir", - }, }, workspace={ - "creator_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "date_created": "2019-12-27T18:11:19.117Z", - "date_updated": "2019-12-27T18:11:19.117Z", - "invite_count": 0, - "member_count": 0, "name": "Openlayer", - "period_end_date": "2019-12-27T18:11:19.117Z", - "period_start_date": "2019-12-27T18:11:19.117Z", - "project_count": 0, "slug": "openlayer", - "invite_code": "inviteCode", - "monthly_usage": [ - { - "execution_time_ms": 0, - "month_year": "2019-12-27", - "prediction_count": 0, - } - ], "saml_only_access": True, "wildcard_domains": ["string"], }, From aefb7d93a78f972467a3f70a17c06d9e451817b8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 03:19:51 +0000 Subject: [PATCH 07/22] fix(tests): correctly generate examples with writeOnly fields --- tests/api_resources/inference_pipelines/test_rows.py | 2 ++ tests/api_resources/projects/test_inference_pipelines.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/api_resources/inference_pipelines/test_rows.py b/tests/api_resources/inference_pipelines/test_rows.py index d9694072..bef1c42f 100644 --- a/tests/api_resources/inference_pipelines/test_rows.py +++ b/tests/api_resources/inference_pipelines/test_rows.py @@ -35,6 +35,7 @@ def test_method_update_with_all_params(self, client: Openlayer) -> None: config={ "ground_truth_column_name": "ground_truth", "human_feedback_column_name": "human_feedback", + "inference_id_column_name": "id", "latency_column_name": "latency", "timestamp_column_name": "timestamp", }, @@ -100,6 +101,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpenlayer) config={ "ground_truth_column_name": "ground_truth", "human_feedback_column_name": "human_feedback", + "inference_id_column_name": "id", "latency_column_name": "latency", "timestamp_column_name": "timestamp", }, diff --git a/tests/api_resources/projects/test_inference_pipelines.py b/tests/api_resources/projects/test_inference_pipelines.py index 5983c59d..ea0bb5b6 100644 --- a/tests/api_resources/projects/test_inference_pipelines.py +++ b/tests/api_resources/projects/test_inference_pipelines.py @@ -43,6 +43,7 @@ def test_method_create_with_all_params(self, client: Openlayer) -> None: workspace={ "name": "Openlayer", "slug": "openlayer", + "invite_code": "inviteCode", "saml_only_access": True, "wildcard_domains": ["string"], }, @@ -161,6 +162,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) workspace={ "name": "Openlayer", "slug": "openlayer", + "invite_code": "inviteCode", "saml_only_access": True, "wildcard_domains": ["string"], }, From 32452f0ac8f3a321a81fb7bd340fa6ced4c5c648 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 03:19:47 +0000 Subject: [PATCH 08/22] chore(internal): change default timeout to an int --- src/openlayer/_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openlayer/_constants.py b/src/openlayer/_constants.py index a2ac3b6f..6ddf2c71 100644 --- a/src/openlayer/_constants.py +++ b/src/openlayer/_constants.py @@ -6,7 +6,7 @@ OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 1 minute -DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0) +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) DEFAULT_MAX_RETRIES = 2 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) From a85525a6cc9e3ac81ba1cd5fb534e120c1580067 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 03:21:27 +0000 Subject: [PATCH 09/22] chore(internal): bummp ruff dependency --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- scripts/utils/ruffen-docs.py | 4 ++-- src/openlayer/_models.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f166fcb0..c52a47fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -183,7 +183,7 @@ select = [ "T201", "T203", # misuse of typing.TYPE_CHECKING - "TCH004", + "TC004", # import rules "TID251", ] diff --git a/requirements-dev.lock b/requirements-dev.lock index dcd49711..96bb136c 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -98,7 +98,7 @@ requests-toolbelt==1.0.0 # via openlayer respx==0.22.0 rich==13.7.1 -ruff==0.6.9 +ruff==0.9.4 setuptools==68.2.2 # via nodeenv six==1.16.0 diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py index 37b3d94f..0cf2bd2f 100644 --- a/scripts/utils/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str: with _collect_error(match): code = format_code_block(code) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" def _pycon_match(match: Match[str]) -> str: code = "" @@ -97,7 +97,7 @@ def finish_fragment() -> None: def _md_pycon_match(match: Match[str]) -> str: code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" src = MD_RE.sub(_md_match, src) src = MD_PYCON_RE.sub(_md_pycon_match, src) diff --git a/src/openlayer/_models.py b/src/openlayer/_models.py index 9a918aab..12c34b7d 100644 --- a/src/openlayer/_models.py +++ b/src/openlayer/_models.py @@ -172,7 +172,7 @@ def to_json( @override def __str__(self) -> str: # mypy complains about an invalid self arg - return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc] + return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] # Override the 'construct' method in a way that supports recursive parsing without validation. # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. From 919377ee8e73ad8ca39d5cead7f85c3e934b7bc1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:30:21 +0000 Subject: [PATCH 10/22] feat(client): send `X-Stainless-Read-Timeout` header --- src/openlayer/_base_client.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/openlayer/_base_client.py b/src/openlayer/_base_client.py index 0467e487..07e3f966 100644 --- a/src/openlayer/_base_client.py +++ b/src/openlayer/_base_client.py @@ -405,10 +405,17 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: headers[idempotency_header] = options.idempotency_key or self._idempotency_key() - # Don't set the retry count header if it was already set or removed by the caller. We check + # Don't set these headers if they were already set or removed by the caller. We check # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. - if "x-stainless-retry-count" not in (header.lower() for header in custom_headers): + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-stainless-retry-count" not in lower_custom_headers: headers["x-stainless-retry-count"] = str(retries_taken) + if "x-stainless-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-stainless-read-timeout"] = str(timeout) return headers From df06aaa91ee17410b96b28e897c5559f67cbc829 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 03:27:28 +0000 Subject: [PATCH 11/22] chore(internal): fix type traversing dictionary params --- src/openlayer/_utils/_transform.py | 12 +++++++++++- tests/test_transform.py | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/openlayer/_utils/_transform.py b/src/openlayer/_utils/_transform.py index a6b62cad..18afd9d8 100644 --- a/src/openlayer/_utils/_transform.py +++ b/src/openlayer/_utils/_transform.py @@ -25,7 +25,7 @@ is_annotated_type, strip_annotated_type, ) -from .._compat import model_dump, is_typeddict +from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -164,9 +164,14 @@ def _transform_recursive( inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return _transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) @@ -307,9 +312,14 @@ async def _async_transform_recursive( inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return await _async_transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) diff --git a/tests/test_transform.py b/tests/test_transform.py index 74ddb20d..043b1020 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -2,7 +2,7 @@ import io import pathlib -from typing import Any, List, Union, TypeVar, Iterable, Optional, cast +from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict @@ -388,6 +388,15 @@ def my_iter() -> Iterable[Baz8]: } +@parametrize +@pytest.mark.asyncio +async def test_dictionary_items(use_async: bool) -> None: + class DictItems(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} + + class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] From a92096519c3a1d2ecaad5595029231faeafb09ed Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 03:30:47 +0000 Subject: [PATCH 12/22] chore(internal): minor type handling changes --- src/openlayer/_models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/openlayer/_models.py b/src/openlayer/_models.py index 12c34b7d..c4401ff8 100644 --- a/src/openlayer/_models.py +++ b/src/openlayer/_models.py @@ -426,10 +426,16 @@ def construct_type(*, value: object, type_: object) -> object: If the given value does not match the expected type then it is returned as-is. """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` @@ -446,7 +452,7 @@ def construct_type(*, value: object, type_: object) -> object: if is_union(origin): try: - return validate_type(type_=cast("type[object]", type_), value=value) + return validate_type(type_=cast("type[object]", original_type or type_), value=value) except Exception: pass From c7a899524ea9b3ff1218a0e03868a8647ee46a08 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 04:27:07 +0000 Subject: [PATCH 13/22] chore(internal): update client tests --- tests/test_client.py | 150 +++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 68 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 089f0652..21637f11 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,6 +23,7 @@ from openlayer import Openlayer, AsyncOpenlayer, APIResponseValidationError from openlayer._types import Omit +from openlayer._utils import maybe_transform from openlayer._models import BaseModel, FinalRequestOptions from openlayer._constants import RAW_RESPONSE_HEADER from openlayer._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError @@ -32,6 +33,7 @@ BaseClient, make_request_options, ) +from openlayer.types.inference_pipelines.data_stream_params import DataStreamParams from .utils import update_env @@ -730,23 +732,26 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No "/inference-pipelines/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/data-stream", body=cast( object, - dict( - config={ - "input_variable_names": ["user_query"], - "output_column_name": "output", - "num_of_token_column_name": "tokens", - "cost_column_name": "cost", - "timestamp_column_name": "timestamp", - }, - rows=[ - { - "user_query": "what is the meaning of life?", - "output": "42", - "tokens": 7, - "cost": 0.02, - "timestamp": 1610000000, - } - ], + maybe_transform( + dict( + config={ + "input_variable_names": ["user_query"], + "output_column_name": "output", + "num_of_token_column_name": "tokens", + "cost_column_name": "cost", + "timestamp_column_name": "timestamp", + }, + rows=[ + { + "user_query": "what is the meaning of life?", + "output": "42", + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, + } + ], + ), + DataStreamParams, ), ), cast_to=httpx.Response, @@ -767,23 +772,26 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non "/inference-pipelines/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/data-stream", body=cast( object, - dict( - config={ - "input_variable_names": ["user_query"], - "output_column_name": "output", - "num_of_token_column_name": "tokens", - "cost_column_name": "cost", - "timestamp_column_name": "timestamp", - }, - rows=[ - { - "user_query": "what is the meaning of life?", - "output": "42", - "tokens": 7, - "cost": 0.02, - "timestamp": 1610000000, - } - ], + maybe_transform( + dict( + config={ + "input_variable_names": ["user_query"], + "output_column_name": "output", + "num_of_token_column_name": "tokens", + "cost_column_name": "cost", + "timestamp_column_name": "timestamp", + }, + rows=[ + { + "user_query": "what is the meaning of life?", + "output": "42", + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, + } + ], + ), + DataStreamParams, ), ), cast_to=httpx.Response, @@ -1603,23 +1611,26 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) "/inference-pipelines/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/data-stream", body=cast( object, - dict( - config={ - "input_variable_names": ["user_query"], - "output_column_name": "output", - "num_of_token_column_name": "tokens", - "cost_column_name": "cost", - "timestamp_column_name": "timestamp", - }, - rows=[ - { - "user_query": "what is the meaning of life?", - "output": "42", - "tokens": 7, - "cost": 0.02, - "timestamp": 1610000000, - } - ], + maybe_transform( + dict( + config={ + "input_variable_names": ["user_query"], + "output_column_name": "output", + "num_of_token_column_name": "tokens", + "cost_column_name": "cost", + "timestamp_column_name": "timestamp", + }, + rows=[ + { + "user_query": "what is the meaning of life?", + "output": "42", + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, + } + ], + ), + DataStreamParams, ), ), cast_to=httpx.Response, @@ -1640,23 +1651,26 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) "/inference-pipelines/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/data-stream", body=cast( object, - dict( - config={ - "input_variable_names": ["user_query"], - "output_column_name": "output", - "num_of_token_column_name": "tokens", - "cost_column_name": "cost", - "timestamp_column_name": "timestamp", - }, - rows=[ - { - "user_query": "what is the meaning of life?", - "output": "42", - "tokens": 7, - "cost": 0.02, - "timestamp": 1610000000, - } - ], + maybe_transform( + dict( + config={ + "input_variable_names": ["user_query"], + "output_column_name": "output", + "num_of_token_column_name": "tokens", + "cost_column_name": "cost", + "timestamp_column_name": "timestamp", + }, + rows=[ + { + "user_query": "what is the meaning of life?", + "output": "42", + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, + } + ], + ), + DataStreamParams, ), ), cast_to=httpx.Response, From 1aa358aefbee3ddb9c401eb3e6838b063ba26f1e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2025 04:02:22 +0000 Subject: [PATCH 14/22] fix: asyncify on non-asyncio runtimes --- src/openlayer/_utils/_sync.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/openlayer/_utils/_sync.py b/src/openlayer/_utils/_sync.py index 8b3aaf2b..ad7ec71b 100644 --- a/src/openlayer/_utils/_sync.py +++ b/src/openlayer/_utils/_sync.py @@ -7,16 +7,20 @@ from typing import Any, TypeVar, Callable, Awaitable from typing_extensions import ParamSpec +import anyio +import sniffio +import anyio.to_thread + T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") if sys.version_info >= (3, 9): - to_thread = asyncio.to_thread + _asyncio_to_thread = asyncio.to_thread else: # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread # for Python 3.8 support - async def to_thread( + async def _asyncio_to_thread( func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs ) -> Any: """Asynchronously run function *func* in a separate thread. @@ -34,6 +38,17 @@ async def to_thread( return await loop.run_in_executor(None, func_call) +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await _asyncio_to_thread(func, *args, **kwargs) + + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + # inspired by `asyncer`, https://github.com/tiangolo/asyncer def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ From 6a582f73748f4c628cd55dd4781792f8ba82426b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 06:14:29 +0000 Subject: [PATCH 15/22] feat(client): allow passing `NotGiven` for body fix(client): mark some request bodies as optional --- src/openlayer/_base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openlayer/_base_client.py b/src/openlayer/_base_client.py index 07e3f966..2fe4231a 100644 --- a/src/openlayer/_base_client.py +++ b/src/openlayer/_base_client.py @@ -505,7 +505,7 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, + json=json_data if is_given(json_data) else None, files=files, **kwargs, ) From 9bc507d3197627087b7139ee3c2f9e28c4075c95 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 22 Feb 2025 04:37:24 +0000 Subject: [PATCH 16/22] chore(internal): fix devcontainers setup --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e75..55d20255 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,4 +6,4 @@ USER vscode RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b1..c17fdc16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. From 0124a2338534da8f0d707d9c6d6f5e5576d6999f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 03:53:16 +0000 Subject: [PATCH 17/22] chore(internal): properly set __pydantic_private__ --- src/openlayer/_base_client.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/openlayer/_base_client.py b/src/openlayer/_base_client.py index 2fe4231a..38a3b467 100644 --- a/src/openlayer/_base_client.py +++ b/src/openlayer/_base_client.py @@ -59,7 +59,7 @@ ModelBuilderProtocol, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import model_copy, model_dump +from ._compat import PYDANTIC_V2, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -203,6 +203,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -288,6 +291,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options From 1946b4f202142fe9a58c11d5f74870def6582d9b Mon Sep 17 00:00:00 2001 From: meorphis Date: Fri, 14 Mar 2025 10:57:18 -0400 Subject: [PATCH 18/22] chore(internal): fix workflows --- .github/workflows/create-releases.yml | 38 --------------------------- .github/workflows/publish-pypi.yml | 8 ++++-- .github/workflows/release-doctor.yml | 1 - bin/check-release-environment | 4 --- 4 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 .github/workflows/create-releases.yml diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml deleted file mode 100644 index b5d9a362..00000000 --- a/.github/workflows/create-releases.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Create releases -on: - schedule: - - cron: '0 5 * * *' # every day at 5am UTC - push: - branches: - - main - -jobs: - release: - name: release - if: github.ref == 'refs/heads/main' && github.repository == 'openlayer-ai/openlayer-python' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: stainless-api/trigger-release-please@v1 - id: release - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Install Rye - if: ${{ steps.release.outputs.releases_created }} - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.35.0' - RYE_INSTALL_OPTION: '--yes' - - - name: Publish to PyPI - if: ${{ steps.release.outputs.releases_created }} - run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.OPENLAYER_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 5a6c2318..60b414a0 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,9 +1,13 @@ -# workflow for re-running publishing to PyPI in case it fails for some reason -# you can run this workflow by navigating to https://www.github.com/openlayer-ai/openlayer-python/actions/workflows/publish-pypi.yml +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to PyPI in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/openlayer-ai/openlayer-python/actions/workflows/publish-pypi.yml name: Publish PyPI on: workflow_dispatch: + release: + types: [published] + jobs: publish: name: publish diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 95f1a185..d6d56f28 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -18,5 +18,4 @@ jobs: run: | bash ./bin/check-release-environment env: - STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} PYPI_TOKEN: ${{ secrets.OPENLAYER_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/bin/check-release-environment b/bin/check-release-environment index b737e128..c0077294 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,10 +2,6 @@ errors=() -if [ -z "${STAINLESS_API_KEY}" ]; then - errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") -fi - if [ -z "${PYPI_TOKEN}" ]; then errors+=("The OPENLAYER_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi From 395275b0f996f2b4eb49857530e72f9fe64b853a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 04:21:40 +0000 Subject: [PATCH 19/22] chore(internal): codegen related update (#429) --- requirements-dev.lock | 1 + requirements.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements-dev.lock b/requirements-dev.lock index 96bb136c..3a355bb0 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.6.0 diff --git a/requirements.lock b/requirements.lock index 96517994..3d67e780 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.6.0 From 9fe86fef481775181a52d3e4f9249c4405d4bb24 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 04:29:49 +0000 Subject: [PATCH 20/22] chore(internal): bump rye to 0.44.0 (#430) --- .devcontainer/Dockerfile | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 55d20255..ff261bad 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash +RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e503784c..5ac5f63f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Install dependencies diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 60b414a0..3779ab92 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -21,7 +21,7 @@ jobs: curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Publish to PyPI From 3a8b9c104e28589248d3208f92d8cda3bee1364e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 04:36:30 +0000 Subject: [PATCH 21/22] fix(types): handle more discriminated union shapes (#431) --- src/openlayer/_models.py | 7 +++++-- tests/test_models.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/openlayer/_models.py b/src/openlayer/_models.py index c4401ff8..b51a1bf5 100644 --- a/src/openlayer/_models.py +++ b/src/openlayer/_models.py @@ -65,7 +65,7 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: - from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema __all__ = ["BaseModel", "GenericModel"] @@ -646,15 +646,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + if schema["type"] != "model": return None + schema = cast("ModelSchema", schema) fields_schema = schema["schema"] if fields_schema["type"] != "model-fields": return None fields_schema = cast("ModelFieldsSchema", fields_schema) - field = fields_schema["fields"].get(field_name) if not field: return None diff --git a/tests/test_models.py b/tests/test_models.py index 5108c6a7..d9f8dc55 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -854,3 +854,35 @@ class Model(BaseModel): m = construct_type(value={"cls": "foo"}, type_=Model) assert isinstance(m, Model) assert isinstance(m.cls, str) + + +def test_discriminated_union_case() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["b"] + + data: List[Union[A, object]] + + class ModelA(BaseModel): + type: Literal["modelA"] + + data: int + + class ModelB(BaseModel): + type: Literal["modelB"] + + required: str + + data: Union[A, B] + + # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required` + m = construct_type( + value={"type": "modelB", "data": {"type": "a", "data": True}}, + type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]), + ) + + assert isinstance(m, ModelB) From 5e2ab9d2d4079b4ef139515ef126690160fc1a92 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 04:36:55 +0000 Subject: [PATCH 22/22] release: 0.2.0-alpha.46 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/openlayer/_version.py | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b8327a3..cb29bb77 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.2.0-alpha.45" + ".": "0.2.0-alpha.46" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e52506..b8176217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.2.0-alpha.46 (2025-03-15) + +Full Changelog: [v0.2.0-alpha.45...v0.2.0-alpha.46](https://github.com/openlayer-ai/openlayer-python/compare/v0.2.0-alpha.45...v0.2.0-alpha.46) + +### Features + +* **api:** api update ([10f1de0](https://github.com/openlayer-ai/openlayer-python/commit/10f1de0a71b489ec6e479af5fd8c33bc4f2cc63a)) +* **client:** allow passing `NotGiven` for body ([6a582f7](https://github.com/openlayer-ai/openlayer-python/commit/6a582f73748f4c628cd55dd4781792f8ba82426b)) +* **client:** send `X-Stainless-Read-Timeout` header ([919377e](https://github.com/openlayer-ai/openlayer-python/commit/919377ee8e73ad8ca39d5cead7f85c3e934b7bc1)) + + +### Bug Fixes + +* asyncify on non-asyncio runtimes ([1aa358a](https://github.com/openlayer-ai/openlayer-python/commit/1aa358aefbee3ddb9c401eb3e6838b063ba26f1e)) +* **client:** mark some request bodies as optional ([6a582f7](https://github.com/openlayer-ai/openlayer-python/commit/6a582f73748f4c628cd55dd4781792f8ba82426b)) +* **tests:** correctly generate examples with writeOnly fields ([aefb7d9](https://github.com/openlayer-ai/openlayer-python/commit/aefb7d93a78f972467a3f70a17c06d9e451817b8)) +* **types:** handle more discriminated union shapes ([#431](https://github.com/openlayer-ai/openlayer-python/issues/431)) ([3a8b9c1](https://github.com/openlayer-ai/openlayer-python/commit/3a8b9c104e28589248d3208f92d8cda3bee1364e)) + + +### Chores + +* **internal:** bummp ruff dependency ([a85525a](https://github.com/openlayer-ai/openlayer-python/commit/a85525a6cc9e3ac81ba1cd5fb534e120c1580067)) +* **internal:** bump rye to 0.44.0 ([#430](https://github.com/openlayer-ai/openlayer-python/issues/430)) ([9fe86fe](https://github.com/openlayer-ai/openlayer-python/commit/9fe86fef481775181a52d3e4f9249c4405d4bb24)) +* **internal:** change default timeout to an int ([32452f0](https://github.com/openlayer-ai/openlayer-python/commit/32452f0ac8f3a321a81fb7bd340fa6ced4c5c648)) +* **internal:** codegen related update ([dfd7861](https://github.com/openlayer-ai/openlayer-python/commit/dfd7861657bbd5f761649b5f956cb9c85e9bd1e4)) +* **internal:** codegen related update ([c87c92d](https://github.com/openlayer-ai/openlayer-python/commit/c87c92ded5591542b9c939c775fa2d09fb0885c5)) +* **internal:** codegen related update ([#425](https://github.com/openlayer-ai/openlayer-python/issues/425)) ([ec47eb9](https://github.com/openlayer-ai/openlayer-python/commit/ec47eb9f03007a5efa8c194ab98d0aa1377720b9)) +* **internal:** codegen related update ([#429](https://github.com/openlayer-ai/openlayer-python/issues/429)) ([395275b](https://github.com/openlayer-ai/openlayer-python/commit/395275b0f996f2b4eb49857530e72f9fe64b853a)) +* **internal:** fix devcontainers setup ([9bc507d](https://github.com/openlayer-ai/openlayer-python/commit/9bc507d3197627087b7139ee3c2f9e28c4075c95)) +* **internal:** fix type traversing dictionary params ([df06aaa](https://github.com/openlayer-ai/openlayer-python/commit/df06aaa91ee17410b96b28e897c5559f67cbc829)) +* **internal:** fix workflows ([1946b4f](https://github.com/openlayer-ai/openlayer-python/commit/1946b4f202142fe9a58c11d5f74870def6582d9b)) +* **internal:** minor type handling changes ([a920965](https://github.com/openlayer-ai/openlayer-python/commit/a92096519c3a1d2ecaad5595029231faeafb09ed)) +* **internal:** properly set __pydantic_private__ ([0124a23](https://github.com/openlayer-ai/openlayer-python/commit/0124a2338534da8f0d707d9c6d6f5e5576d6999f)) +* **internal:** remove extra empty newlines ([#428](https://github.com/openlayer-ai/openlayer-python/issues/428)) ([7111d6d](https://github.com/openlayer-ai/openlayer-python/commit/7111d6d4a8a8524aadbc402ea4761dba2b377170)) +* **internal:** update client tests ([c7a8995](https://github.com/openlayer-ai/openlayer-python/commit/c7a899524ea9b3ff1218a0e03868a8647ee46a08)) + ## 0.2.0-alpha.45 (2025-03-13) Full Changelog: [v0.2.0-alpha.44...v0.2.0-alpha.45](https://github.com/openlayer-ai/openlayer-python/compare/v0.2.0-alpha.44...v0.2.0-alpha.45) diff --git a/pyproject.toml b/pyproject.toml index c52a47fc..10217f22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openlayer" -version = "0.2.0-alpha.45" +version = "0.2.0-alpha.46" description = "The official Python library for the openlayer API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/openlayer/_version.py b/src/openlayer/_version.py index 6a778d14..1fcf2f30 100644 --- a/src/openlayer/_version.py +++ b/src/openlayer/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "openlayer" -__version__ = "0.2.0-alpha.45" # x-release-please-version +__version__ = "0.2.0-alpha.46" # x-release-please-version