Skip to content

Commit 9f911df

Browse files
authored
Merge pull request #35 from digitalocean/release-please--branches--main--changes--next
release: 3.0.0-beta.5
2 parents b35f30a + 6507233 commit 9f911df

File tree

15 files changed

+206
-329
lines changed

15 files changed

+206
-329
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ jobs:
3636
run: ./scripts/lint
3737

3838
build:
39-
if: github.repository == 'stainless-sdks/gradient-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
39+
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
4040
timeout-minutes: 10
4141
name: build
4242
permissions:
4343
contents: read
4444
id-token: write
45-
runs-on: depot-ubuntu-24.04
45+
runs-on: ${{ github.repository == 'stainless-sdks/gradient-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
4646
steps:
4747
- uses: actions/checkout@v4
4848

@@ -61,12 +61,14 @@ jobs:
6161
run: rye build
6262

6363
- name: Get GitHub OIDC Token
64+
if: github.repository == 'stainless-sdks/gradient-python'
6465
id: github-oidc
6566
uses: actions/github-script@v6
6667
with:
6768
script: core.setOutput('github_token', await core.getIDToken());
6869

6970
- name: Upload tarball
71+
if: github.repository == 'stainless-sdks/gradient-python'
7072
env:
7173
URL: https://pkg.stainless.com/s
7274
AUTH: ${{ steps.github-oidc.outputs.github_token }}

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "3.0.0-beta.4"
2+
".": "3.0.0-beta.5"
33
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 170
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/digitalocean%2Fgradient-9aca3802735e1375125412aa28ac36bf2175144b8218610a73d2e7f775694dff.yml
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/digitalocean%2Fgradient-621c3ebf5011c5ca508f78fccbea17de4ca6b35bfe99578c1ae2265021578d6f.yml
33
openapi_spec_hash: e29d14e3e4679fcf22b3e760e49931b1
4-
config_hash: 99e3cd5dde0beb796f4547410869f726
4+
config_hash: 6c8d569b60ae6536708a165b72ff838f

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## 3.0.0-beta.5 (2025-09-08)
4+
5+
Full Changelog: [v3.0.0-beta.4...v3.0.0-beta.5](https://github.com/digitalocean/gradient-python/compare/v3.0.0-beta.4...v3.0.0-beta.5)
6+
7+
### Features
8+
9+
* **api:** manual updates ([044a233](https://github.com/digitalocean/gradient-python/commit/044a2339f9ae89facbed403d8240d1e4cf3e9c1f))
10+
* **api:** manual updates ([0e8fd1b](https://github.com/digitalocean/gradient-python/commit/0e8fd1b364751ec933cadf02be693afa63a67029))
11+
12+
13+
### Bug Fixes
14+
15+
* avoid newer type syntax ([3d5c35c](https://github.com/digitalocean/gradient-python/commit/3d5c35ca11b4c7344308f7fbd7cd98ec44dd65a0))
16+
17+
18+
### Chores
19+
20+
* **internal:** add Sequence related utils ([2997cfc](https://github.com/digitalocean/gradient-python/commit/2997cfc25bf46b4cc9faf9f0f22cb4680cadca8b))
21+
* **internal:** change ci workflow machines ([5f41b3d](https://github.com/digitalocean/gradient-python/commit/5f41b3d956bf1ae25f90b862d5057c16b06e78a3))
22+
* **internal:** update pyright exclude list ([2a0d1a2](https://github.com/digitalocean/gradient-python/commit/2a0d1a2b174990d6b081ff764b13949b4dfa107f))
23+
* update github action ([369c5d9](https://github.com/digitalocean/gradient-python/commit/369c5d982cfadfaaaeda9481b2c9249e3f87423d))
24+
325
## 3.0.0-beta.4 (2025-08-12)
426

527
Full Changelog: [v3.0.0-beta.3...v3.0.0-beta.4](https://github.com/digitalocean/gradient-python/compare/v3.0.0-beta.3...v3.0.0-beta.4)

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "gradient"
3-
version = "3.0.0-beta.4"
3+
version = "3.0.0-beta.5"
44
description = "The official Python library for the Gradient API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -131,7 +131,12 @@ filterwarnings = ["error"]
131131
typeCheckingMode = "strict"
132132
pythonVersion = "3.8"
133133

134-
exclude = ["_dev", ".venv", ".nox"]
134+
exclude = [
135+
"_dev",
136+
".venv",
137+
".nox",
138+
".git",
139+
]
135140

136141
reportImplicitOverride = true
137142
reportOverlappingOverload = false

src/gradient/_client.py

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ def __init__(
113113
- `access_token` from `DIGITALOCEAN_ACCESS_TOKEN`
114114
- `model_access_key` from `GRADIENT_MODEL_ACCESS_KEY`
115115
- `agent_access_key` from `GRADIENT_AGENT_ACCESS_KEY`
116+
- `agent_endpoint` from `GRADIENT_AGENT_ENDPOINT`
117+
- `inference_endpoint` from `GRADIENT_INFERENCE_ENDPOINT`
116118
"""
117119
if access_token is None:
118120
if api_key is not None:
@@ -149,10 +151,7 @@ def __init__(
149151
self._agent_endpoint = agent_endpoint
150152

151153
if inference_endpoint is None:
152-
inference_endpoint = os.environ.get("GRADIENT_INFERENCE_ENDPOINT")
153-
if inference_endpoint is None:
154-
inference_endpoint = "https://inference.do-ai.run"
155-
154+
inference_endpoint = os.environ.get("GRADIENT_INFERENCE_ENDPOINT") or "inference.do-ai.run"
156155
self.inference_endpoint = inference_endpoint
157156

158157
if base_url is None:
@@ -267,9 +266,7 @@ def default_headers(self) -> dict[str, str | Omit]:
267266

268267
@override
269268
def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
270-
if (
271-
self.access_token or self.agent_access_key or self.model_access_key
272-
) and headers.get("Authorization"):
269+
if (self.access_token or self.agent_access_key or self.model_access_key) and headers.get("Authorization"):
273270
return
274271
if isinstance(custom_headers.get("Authorization"), Omit):
275272
return
@@ -303,14 +300,10 @@ def copy(
303300
Create a new client instance re-using the same options given to the current client with optional overriding.
304301
"""
305302
if default_headers is not None and set_default_headers is not None:
306-
raise ValueError(
307-
"The `default_headers` and `set_default_headers` arguments are mutually exclusive"
308-
)
303+
raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
309304

310305
if default_query is not None and set_default_query is not None:
311-
raise ValueError(
312-
"The `default_query` and `set_default_query` arguments are mutually exclusive"
313-
)
306+
raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
314307

315308
headers = self._custom_headers
316309
if default_headers is not None:
@@ -358,14 +351,10 @@ def _make_status_error(
358351
return _exceptions.BadRequestError(err_msg, response=response, body=body)
359352

360353
if response.status_code == 401:
361-
return _exceptions.AuthenticationError(
362-
err_msg, response=response, body=body
363-
)
354+
return _exceptions.AuthenticationError(err_msg, response=response, body=body)
364355

365356
if response.status_code == 403:
366-
return _exceptions.PermissionDeniedError(
367-
err_msg, response=response, body=body
368-
)
357+
return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
369358

370359
if response.status_code == 404:
371360
return _exceptions.NotFoundError(err_msg, response=response, body=body)
@@ -374,17 +363,13 @@ def _make_status_error(
374363
return _exceptions.ConflictError(err_msg, response=response, body=body)
375364

376365
if response.status_code == 422:
377-
return _exceptions.UnprocessableEntityError(
378-
err_msg, response=response, body=body
379-
)
366+
return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
380367

381368
if response.status_code == 429:
382369
return _exceptions.RateLimitError(err_msg, response=response, body=body)
383370

384371
if response.status_code >= 500:
385-
return _exceptions.InternalServerError(
386-
err_msg, response=response, body=body
387-
)
372+
return _exceptions.InternalServerError(err_msg, response=response, body=body)
388373
return APIStatusError(err_msg, response=response, body=body)
389374

390375

@@ -432,6 +417,8 @@ def __init__(
432417
- `access_token` from `DIGITALOCEAN_ACCESS_TOKEN`
433418
- `model_access_key` from `GRADIENT_MODEL_ACCESS_KEY`
434419
- `agent_access_key` from `GRADIENT_AGENT_ACCESS_KEY`
420+
- `agent_endpoint` from `GRADIENT_AGENT_ENDPOINT`
421+
- `inference_endpoint` from `GRADIENT_INFERENCE_ENDPOINT`
435422
"""
436423
if access_token is None:
437424
if api_key is not None:
@@ -463,8 +450,12 @@ def __init__(
463450
agent_access_key = os.environ.get("GRADIENT_AGENT_KEY")
464451
self.agent_access_key = agent_access_key
465452

453+
if agent_endpoint is None:
454+
agent_endpoint = os.environ.get("GRADIENT_AGENT_ENDPOINT")
466455
self._agent_endpoint = agent_endpoint
467456

457+
if inference_endpoint is None:
458+
inference_endpoint = os.environ.get("GRADIENT_INFERENCE_ENDPOINT") or "inference.do-ai.run"
468459
self.inference_endpoint = inference_endpoint
469460

470461
if base_url is None:
@@ -579,9 +570,7 @@ def default_headers(self) -> dict[str, str | Omit]:
579570

580571
@override
581572
def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
582-
if (
583-
self.access_token or self.agent_access_key or self.model_access_key
584-
) and headers.get("Authorization"):
573+
if (self.access_token or self.agent_access_key or self.model_access_key) and headers.get("Authorization"):
585574
return
586575
if isinstance(custom_headers.get("Authorization"), Omit):
587576
return
@@ -615,14 +604,10 @@ def copy(
615604
Create a new client instance re-using the same options given to the current client with optional overriding.
616605
"""
617606
if default_headers is not None and set_default_headers is not None:
618-
raise ValueError(
619-
"The `default_headers` and `set_default_headers` arguments are mutually exclusive"
620-
)
607+
raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
621608

622609
if default_query is not None and set_default_query is not None:
623-
raise ValueError(
624-
"The `default_query` and `set_default_query` arguments are mutually exclusive"
625-
)
610+
raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
626611

627612
headers = self._custom_headers
628613
if default_headers is not None:
@@ -670,14 +655,10 @@ def _make_status_error(
670655
return _exceptions.BadRequestError(err_msg, response=response, body=body)
671656

672657
if response.status_code == 401:
673-
return _exceptions.AuthenticationError(
674-
err_msg, response=response, body=body
675-
)
658+
return _exceptions.AuthenticationError(err_msg, response=response, body=body)
676659

677660
if response.status_code == 403:
678-
return _exceptions.PermissionDeniedError(
679-
err_msg, response=response, body=body
680-
)
661+
return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
681662

682663
if response.status_code == 404:
683664
return _exceptions.NotFoundError(err_msg, response=response, body=body)
@@ -686,17 +667,13 @@ def _make_status_error(
686667
return _exceptions.ConflictError(err_msg, response=response, body=body)
687668

688669
if response.status_code == 422:
689-
return _exceptions.UnprocessableEntityError(
690-
err_msg, response=response, body=body
691-
)
670+
return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
692671

693672
if response.status_code == 429:
694673
return _exceptions.RateLimitError(err_msg, response=response, body=body)
695674

696675
if response.status_code >= 500:
697-
return _exceptions.InternalServerError(
698-
err_msg, response=response, body=body
699-
)
676+
return _exceptions.InternalServerError(err_msg, response=response, body=body)
700677
return APIStatusError(err_msg, response=response, body=body)
701678

702679

@@ -915,9 +892,7 @@ def knowledge_bases(
915892
AsyncKnowledgeBasesResourceWithStreamingResponse,
916893
)
917894

918-
return AsyncKnowledgeBasesResourceWithStreamingResponse(
919-
self._client.knowledge_bases
920-
)
895+
return AsyncKnowledgeBasesResourceWithStreamingResponse(self._client.knowledge_bases)
921896

922897
@cached_property
923898
def models(self) -> models.AsyncModelsResourceWithStreamingResponse:

src/gradient/_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def model_dump(
304304
exclude_none=exclude_none,
305305
)
306306

307-
return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped
307+
return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped
308308

309309
@override
310310
def model_dump_json(

src/gradient/_types.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@
1313
Mapping,
1414
TypeVar,
1515
Callable,
16+
Iterator,
1617
Optional,
1718
Sequence,
1819
)
19-
from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
20+
from typing_extensions import (
21+
Set,
22+
Literal,
23+
Protocol,
24+
TypeAlias,
25+
TypedDict,
26+
SupportsIndex,
27+
overload,
28+
override,
29+
runtime_checkable,
30+
)
2031

2132
import httpx
2233
import pydantic
@@ -217,3 +228,26 @@ class _GenericAlias(Protocol):
217228
class HttpxSendArgs(TypedDict, total=False):
218229
auth: httpx.Auth
219230
follow_redirects: bool
231+
232+
233+
_T_co = TypeVar("_T_co", covariant=True)
234+
235+
236+
if TYPE_CHECKING:
237+
# This works because str.__contains__ does not accept object (either in typeshed or at runtime)
238+
# https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
239+
class SequenceNotStr(Protocol[_T_co]):
240+
@overload
241+
def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
242+
@overload
243+
def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
244+
def __contains__(self, value: object, /) -> bool: ...
245+
def __len__(self) -> int: ...
246+
def __iter__(self) -> Iterator[_T_co]: ...
247+
def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ...
248+
def count(self, value: Any, /) -> int: ...
249+
def __reversed__(self) -> Iterator[_T_co]: ...
250+
else:
251+
# just point this to a normal `Sequence` at runtime to avoid having to special case
252+
# deserializing our custom sequence type
253+
SequenceNotStr = Sequence

src/gradient/_utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
extract_type_arg as extract_type_arg,
3939
is_iterable_type as is_iterable_type,
4040
is_required_type as is_required_type,
41+
is_sequence_type as is_sequence_type,
4142
is_annotated_type as is_annotated_type,
4243
is_type_alias_type as is_type_alias_type,
4344
strip_annotated_type as strip_annotated_type,

src/gradient/_utils/_typing.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool:
2626
return (get_origin(typ) or typ) == list
2727

2828

29+
def is_sequence_type(typ: type) -> bool:
30+
origin = get_origin(typ) or typ
31+
return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence
32+
33+
2934
def is_iterable_type(typ: type) -> bool:
3035
"""If the given type is `typing.Iterable[T]`"""
3136
origin = get_origin(typ) or typ

0 commit comments

Comments
 (0)