diff --git a/.gitignore b/.gitignore index 87797408..95ceb189 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .prism.log -.vscode _dev __pycache__ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aff3ead3..bf7fe4fa 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-beta.2" + ".": "0.1.0-beta.3" } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5b010307 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.importFormat": "relative", +} diff --git a/CHANGELOG.md b/CHANGELOG.md index d4262368..2eeef144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.1.0-beta.3 (2025-07-25) + +Full Changelog: [v0.1.0-beta.2...v0.1.0-beta.3](https://github.com/digitalocean/gradientai-python/compare/v0.1.0-beta.2...v0.1.0-beta.3) + +### Bug Fixes + +* **parsing:** parse extra field types ([93bea71](https://github.com/digitalocean/gradientai-python/commit/93bea71735195fa3f32de6b64bbc0aaac60a6d6c)) + + +### Chores + +* **project:** add settings file for vscode ([3b597aa](https://github.com/digitalocean/gradientai-python/commit/3b597aa96e1f588506de47d782444992383f5522)) +* update README with new gradient name ([03157fb](https://github.com/digitalocean/gradientai-python/commit/03157fb38616c68568024ab7e426b45d414bf432)) + ## 0.1.0-beta.2 (2025-07-22) Full Changelog: [v0.1.0-beta.1...v0.1.0-beta.2](https://github.com/digitalocean/gradientai-python/compare/v0.1.0-beta.1...v0.1.0-beta.2) diff --git a/README.md b/README.md index 310601a8..18767b69 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# GradientAI Python API library +# Gradient Python API library ### 🚧 Preview Status -> GradientAI SDK is currently in **preview**. It is reasonably stable and suitable for use, but **some features and APIs may still change** as development continues. +> Gradient SDK is currently in **preview**. It is reasonably stable and suitable for use, but **some features and APIs may still change** as development continues. > Use with care in production environments and keep an eye on releases for updates or breaking changes. [![PyPI version](https://img.shields.io/pypi/v/do_gradientai.svg?label=pypi%20(stable))](https://pypi.org/project/do_gradientai/) -[![Docs](https://img.shields.io/badge/Docs-8A2BE2)](https://gradientai-sdk.digitalocean.com/getting-started/overview/) +[![Docs](https://img.shields.io/badge/Docs-8A2BE2)](https://gradientai.digitalocean.com/getting-started/overview/) -The GradientAI Python library provides convenient access to the GradientAI REST API from any Python 3.8+ +The Gradient Python library provides convenient access to the Gradient REST API from any Python 3.8+ 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). @@ -17,7 +17,7 @@ It is generated with [Stainless](https://www.stainless.com/). ## Documentation -The getting started guide can be found on [gradientai-sdk.digitalocean.com](https://gradientai-sdk.digitalocean.com/getting-started/overview). +The getting started guide can be found on [gradient-sdk.digitalocean.com](https://gradient-sdk.digitalocean.com/getting-started/overview). The REST API documentation can be found on [developers.digitalocean.com](https://developers.digitalocean.com/documentation/v2/). The full API of this library can be found in [api.md](api.md). @@ -30,10 +30,10 @@ pip install --pre do_gradientai ## Usage -The GradientAI SDK provides clients for: +The Gradient SDK provides clients for: * DigitalOcean API -* GradientAI Serverless Inference -* GradientAI Agent Inference +* Gradient Serverless Inference +* Gradient Agent Inference The full API of this library can be found in [api.md](api.md). diff --git a/pyproject.toml b/pyproject.toml index c9469187..74463764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "do_gradientai" -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" description = "The official Python library for GradientAI" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/do_gradientai/_models.py b/src/do_gradientai/_models.py index ffcbf67b..b8387ce9 100644 --- a/src/do_gradientai/_models.py +++ b/src/do_gradientai/_models.py @@ -208,14 +208,18 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + if PYDANTIC_V2: - _extra[key] = value + _extra[key] = parsed else: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed object.__setattr__(m, "__dict__", fields_values) @@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if not PYDANTIC_V2: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None + + def is_basemodel(type_: type) -> bool: """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" if is_union(type_): diff --git a/src/do_gradientai/_version.py b/src/do_gradientai/_version.py index a31f70ad..2789c067 100644 --- a/src/do_gradientai/_version.py +++ b/src/do_gradientai/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "do_gradientai" -__version__ = "0.1.0-beta.2" # x-release-please-version +__version__ = "0.1.0-beta.3" # x-release-please-version diff --git a/tests/test_models.py b/tests/test_models.py index bfbef61a..9a3891e3 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone from typing_extensions import Literal, Annotated, TypeAliasType @@ -934,3 +934,30 @@ class Type2(BaseModel): ) assert isinstance(model, Type1) assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now") +def test_extra_properties() -> None: + class Item(BaseModel): + prop: int + + class Model(BaseModel): + __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + other: str + + if TYPE_CHECKING: + + def __getattr__(self, attr: str) -> Item: ... + + model = construct_type( + type_=Model, + value={ + "a": {"prop": 1}, + "other": "foo", + }, + ) + assert isinstance(model, Model) + assert model.a.prop == 1 + assert isinstance(model.a, Item) + assert model.other == "foo"