diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ac5f63f..e8b72361 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 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 344687e1..da3cbca0 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.2.0-alpha.51" + ".": "0.2.0-alpha.52" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index af69569e..9a6e30e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ 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.52 (2025-04-14) + +Full Changelog: [v0.2.0-alpha.51...v0.2.0-alpha.52](https://github.com/openlayer-ai/openlayer-python/compare/v0.2.0-alpha.51...v0.2.0-alpha.52) + +### Features + +* feat: allow publish without ssl verification ([24dbdef](https://github.com/openlayer-ai/openlayer-python/commit/24dbdef53ccb988e6cd807094ae2a15a4e40fa7f)) + + +### Bug Fixes + +* **perf:** optimize some hot paths ([badc2bb](https://github.com/openlayer-ai/openlayer-python/commit/badc2bb1b915c70045a4f9150792746788a61b79)) +* **perf:** skip traversing types for NotGiven values ([afb0108](https://github.com/openlayer-ai/openlayer-python/commit/afb01083b15f4b4f4878176f2d34a74c72ef3c57)) + + +### Chores + +* **internal:** expand CI branch coverage ([121cc4c](https://github.com/openlayer-ai/openlayer-python/commit/121cc4cf1e7276aba8fde9ca216db17242b641ed)) +* **internal:** reduce CI branch coverage ([05f20c8](https://github.com/openlayer-ai/openlayer-python/commit/05f20c8ff1b471a9a3f3d6f688d0cc7d78cf680b)) +* **internal:** slight transform perf improvement ([#448](https://github.com/openlayer-ai/openlayer-python/issues/448)) ([3c5cd0a](https://github.com/openlayer-ai/openlayer-python/commit/3c5cd0a60b3d33248568075ccb3576536d5cfe7e)) +* **tests:** improve enum examples ([#449](https://github.com/openlayer-ai/openlayer-python/issues/449)) ([3508728](https://github.com/openlayer-ai/openlayer-python/commit/350872865c9f574048c4d6acb112ee72f81e5046)) + ## 0.2.0-alpha.51 (2025-04-04) Full Changelog: [v0.2.0-alpha.50...v0.2.0-alpha.51](https://github.com/openlayer-ai/openlayer-python/compare/v0.2.0-alpha.50...v0.2.0-alpha.51) diff --git a/pyproject.toml b/pyproject.toml index 16ba12c0..81f9b604 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openlayer" -version = "0.2.0-alpha.51" +version = "0.2.0-alpha.52" description = "The official Python library for the openlayer API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/openlayer/_utils/_transform.py b/src/openlayer/_utils/_transform.py index 7ac2e17f..b0cc20a7 100644 --- a/src/openlayer/_utils/_transform.py +++ b/src/openlayer/_utils/_transform.py @@ -5,13 +5,15 @@ import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints import anyio import pydantic from ._utils import ( is_list, + is_given, + lru_cache, is_mapping, is_iterable, ) @@ -108,6 +110,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -142,6 +145,10 @@ def _maybe_transform_key(key: str, type_: type) -> str: return key +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + def _transform_recursive( data: object, *, @@ -184,6 +191,15 @@ def _transform_recursive( return cast(object, data) inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -245,6 +261,11 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include `NotGiven` values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -332,6 +353,15 @@ async def _async_transform_recursive( return cast(object, data) inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -393,6 +423,11 @@ async def _async_transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include `NotGiven` values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -400,3 +435,13 @@ async def _async_transform_typeddict( else: result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/openlayer/_utils/_typing.py b/src/openlayer/_utils/_typing.py index 278749b1..1958820f 100644 --- a/src/openlayer/_utils/_typing.py +++ b/src/openlayer/_utils/_typing.py @@ -13,6 +13,7 @@ get_origin, ) +from ._utils import lru_cache from .._types import InheritsGeneric from .._compat import is_union as _is_union @@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): return strip_annotated_type(cast(type, get_args(typ)[0])) diff --git a/src/openlayer/_version.py b/src/openlayer/_version.py index 73709ebe..96631d01 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.51" # x-release-please-version +__version__ = "0.2.0-alpha.52" # x-release-please-version diff --git a/src/openlayer/lib/tracing/tracer.py b/src/openlayer/lib/tracing/tracer.py index 4057ad0d..4e099416 100644 --- a/src/openlayer/lib/tracing/tracer.py +++ b/src/openlayer/lib/tracing/tracer.py @@ -1,25 +1,36 @@ """Module with the logic to create and manage traces and steps.""" +import time import asyncio -import contextvars import inspect import logging -import time -from contextlib import contextmanager +import contextvars +from typing import Any, Dict, List, Tuple, Optional, Awaitable, Generator from functools import wraps -from typing import Any, Awaitable, Dict, Generator, List, Optional, Tuple +from contextlib import contextmanager +from . import enums, steps, traces +from .. import utils from ..._client import Openlayer +from ..._base_client import DefaultHttpxClient from ...types.inference_pipelines.data_stream_params import ConfigLlmData -from .. import utils -from . import enums, steps, traces logger = logging.getLogger(__name__) -_publish = utils.get_env_variable("OPENLAYER_DISABLE_PUBLISH") != "true" +TRUE_LIST = ["true", "on", "1"] + +_publish = utils.get_env_variable("OPENLAYER_DISABLE_PUBLISH") not in TRUE_LIST +_verify_ssl = utils.get_env_variable("OPENLAYER_VERIFY_SSL").lower() in TRUE_LIST _client = None if _publish: - _client = Openlayer() + if _verify_ssl: + _client = Openlayer() + else: + _client = Openlayer( + http_client=DefaultHttpxClient( + verify=False, + ), + ) _current_step = contextvars.ContextVar("current_step") _current_trace = contextvars.ContextVar("current_trace") @@ -142,8 +153,8 @@ def trace( Examples -------- - To trace a function, simply decorate it with the ``@trace()`` decorator. By doing so, - the functions inputs, outputs, and metadata will be automatically logged to your + To trace a function, simply decorate it with the ``@trace()`` decorator. By doing + so, the functions inputs, outputs, and metadata will be automatically logged to your Openlayer project. >>> import os @@ -204,7 +215,8 @@ def wrapper(*func_args, **func_kwargs): log_context(inputs.get(context_kwarg)) else: logger.warning( - "Context kwarg `%s` not found in inputs of the current function.", + "Context kwarg `%s` not found in inputs of the " + "current function.", context_kwarg, ) @@ -235,8 +247,8 @@ def trace_async( Examples -------- - To trace a function, simply decorate it with the ``@trace()`` decorator. By doing so, - the functions inputs, outputs, and metadata will be automatically logged to your + To trace a function, simply decorate it with the ``@trace()`` decorator. By doing + so, the functions inputs, outputs, and metadata will be automatically logged to your Openlayer project. >>> import os @@ -297,7 +309,8 @@ async def wrapper(*func_args, **func_kwargs): log_context(inputs.get(context_kwarg)) else: logger.warning( - "Context kwarg `%s` not found in inputs of the current function.", + "Context kwarg `%s` not found in inputs of the " + "current function.", context_kwarg, ) diff --git a/tests/api_resources/commits/test_test_results.py b/tests/api_resources/commits/test_test_results.py index da776599..83853215 100644 --- a/tests/api_resources/commits/test_test_results.py +++ b/tests/api_resources/commits/test_test_results.py @@ -31,7 +31,7 @@ def test_method_list_with_all_params(self, client: Openlayer) -> None: include_archived=True, page=1, per_page=1, - status="running", + status="passing", type="integrity", ) assert_matches_type(TestResultListResponse, test_result, path=["response"]) @@ -85,7 +85,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) - include_archived=True, page=1, per_page=1, - status="running", + status="passing", type="integrity", ) assert_matches_type(TestResultListResponse, test_result, path=["response"]) diff --git a/tests/api_resources/inference_pipelines/test_test_results.py b/tests/api_resources/inference_pipelines/test_test_results.py index 2d5bc065..210aa423 100644 --- a/tests/api_resources/inference_pipelines/test_test_results.py +++ b/tests/api_resources/inference_pipelines/test_test_results.py @@ -30,7 +30,7 @@ def test_method_list_with_all_params(self, client: Openlayer) -> None: inference_pipeline_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", page=1, per_page=1, - status="running", + status="passing", type="integrity", ) assert_matches_type(TestResultListResponse, test_result, path=["response"]) @@ -83,7 +83,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) - inference_pipeline_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", page=1, per_page=1, - status="running", + status="passing", type="integrity", ) assert_matches_type(TestResultListResponse, test_result, path=["response"]) diff --git a/tests/test_transform.py b/tests/test_transform.py index 043b1020..8c5ab27a 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from openlayer._types import Base64FileInput +from openlayer._types import NOT_GIVEN, Base64FileInput from openlayer._utils import ( PropertyInfo, transform as _transform, @@ -432,3 +432,22 @@ async def test_base64_file_input(use_async: bool) -> None: assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { "foo": "SGVsbG8sIHdvcmxkIQ==" } # type: ignore[comparison-overlap] + + +@parametrize +@pytest.mark.asyncio +async def test_transform_skipping(use_async: bool) -> None: + # lists of ints are left as-is + data = [1, 2, 3] + assert await transform(data, List[int], use_async) is data + + # iterables of ints are converted to a list + data = iter([1, 2, 3]) + assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@pytest.mark.asyncio +async def test_strips_notgiven(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}