Skip to content

Commit 417a954

Browse files
authored
Merge branch 'main' into main
2 parents d6a55bc + dcef3d5 commit 417a954

File tree

18 files changed

+660
-51
lines changed

18 files changed

+660
-51
lines changed

.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.3.0"
2+
".": "3.6.0"
33
}

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 175
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/digitalocean%2Fgradient-40c154e2fdc4fef9ca1cf8329c29b7102bf324acfc9589571d6f3452d1ca579c.yml
3-
openapi_spec_hash: 83a3d092965fde776b29b61f785459f9
4-
config_hash: 8497af1695ff361853c745dd869dc6b9
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/digitalocean%2Fgradient-cb3bf9b21459cad24410206c27a32fd31ef6cf86711700597549dbbd0d634002.yml
3+
openapi_spec_hash: 6a9149a81ba15e7c5c5c1f4d77daad92
4+
config_hash: bad49c3bf949d5168ec3896bedff253a

CHANGELOG.md

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

3+
## 3.6.0 (2025-10-16)
4+
5+
Full Changelog: [v3.5.0...v3.6.0](https://github.com/digitalocean/gradient-python/compare/v3.5.0...v3.6.0)
6+
7+
### Features
8+
9+
* **api:** manual updates ([da88e9e](https://github.com/digitalocean/gradient-python/commit/da88e9eee0adc6152d0d8212305397483be0d686))
10+
11+
12+
### Bug Fixes
13+
14+
* lints ([a1b1fc6](https://github.com/digitalocean/gradient-python/commit/a1b1fc6b7747c00d9bfc2b86c6262e9c123416dc))
15+
* test setup needs all three access keys ([01ac735](https://github.com/digitalocean/gradient-python/commit/01ac735fb965686699df82ec8763b18ceb660972))
16+
17+
## 3.5.0 (2025-10-14)
18+
19+
Full Changelog: [v3.4.0...v3.5.0](https://github.com/digitalocean/gradient-python/compare/v3.4.0...v3.5.0)
20+
21+
### Features
22+
23+
* **api:** update via SDK Studio ([#74](https://github.com/digitalocean/gradient-python/issues/74)) ([e1ab040](https://github.com/digitalocean/gradient-python/commit/e1ab0407e88f5394f5c299940a4b2fe72dbbf70e))
24+
25+
26+
### Chores
27+
28+
* **internal:** detect missing future annotations with ruff ([0fb9f92](https://github.com/digitalocean/gradient-python/commit/0fb9f9254a0f72a721fa73823399e58eec723f1a))
29+
30+
## 3.4.0 (2025-10-09)
31+
32+
Full Changelog: [v3.3.0...v3.4.0](https://github.com/digitalocean/gradient-python/compare/v3.3.0...v3.4.0)
33+
34+
### Features
35+
36+
* **api:** manual updates ([bbd7ddc](https://github.com/digitalocean/gradient-python/commit/bbd7ddccfb3d98f39e61948365b92202b3cc9e33))
37+
338
## 3.3.0 (2025-10-07)
439

540
Full Changelog: [v3.2.0...v3.3.0](https://github.com/digitalocean/gradient-python/compare/v3.2.0...v3.3.0)

README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ client = Gradient(
4444
), # This is the default and can be omitted
4545
)
4646
inference_client = Gradient(
47-
inference_key=os.environ.get(
47+
model_access_key=os.environ.get(
4848
"GRADIENT_MODEL_ACCESS_KEY"
4949
), # This is the default and can be omitted
5050
)
5151
agent_client = Gradient(
52-
agent_key=os.environ.get("GRADIENT_AGENT_ACCESS_KEY"), # This is the default and can be omitted
52+
agent_access_key=os.environ.get(
53+
"GRADIENT_AGENT_ACCESS_KEY"
54+
), # This is the default and can be omitted
5355
agent_endpoint="https://my-agent.agents.do-ai.run",
5456
)
5557

@@ -103,11 +105,7 @@ import os
103105
import asyncio
104106
from gradient import AsyncGradient
105107

106-
client = AsyncGradient(
107-
access_token=os.environ.get(
108-
"DIGITALOCEAN_ACCESS_TOKEN"
109-
), # This is the default and can be omitted
110-
)
108+
client = AsyncGradient()
111109

112110

113111
async def main() -> None:
@@ -149,7 +147,6 @@ from gradient import AsyncGradient
149147

150148
async def main() -> None:
151149
async with AsyncGradient(
152-
access_token="My Access Token",
153150
http_client=DefaultAioHttpClient(),
154151
) as client:
155152
completion = await client.chat.completions.create(

examples/agent_wait_until_ready.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
Example: Wait for Agent Deployment to Complete
3+
4+
This example demonstrates how to use the wait_until_ready() method to wait for
5+
an agent to finish deploying before using it.
6+
"""
7+
8+
from gradient import Gradient
9+
from gradient._exceptions import AgentDeploymentError, AgentDeploymentTimeoutError
10+
11+
# Initialize the Gradient client
12+
client = Gradient()
13+
14+
# Create a new agent
15+
agent_response = client.agents.create(
16+
name="My Agent",
17+
instruction="You are a helpful assistant",
18+
model_uuid="<your-model-uuid>",
19+
region="nyc1",
20+
)
21+
22+
agent_id = agent_response.agent.uuid if agent_response.agent else None
23+
24+
if agent_id:
25+
print(f"Agent created with ID: {agent_id}")
26+
print("Waiting for agent to be ready...")
27+
28+
try:
29+
# Wait for the agent to be deployed and ready
30+
# This will poll the agent status every 5 seconds (default)
31+
# and wait up to 5 minutes (default timeout=300 seconds)
32+
ready_agent = client.agents.wait_until_ready(
33+
agent_id,
34+
poll_interval=5.0, # Check every 5 seconds
35+
timeout=300.0, # Wait up to 5 minutes
36+
)
37+
38+
if ready_agent.agent and ready_agent.agent.deployment:
39+
print(f"Agent is ready! Status: {ready_agent.agent.deployment.status}")
40+
print(f"Agent URL: {ready_agent.agent.url}")
41+
42+
# Now you can use the agent
43+
# ...
44+
45+
except AgentDeploymentError as e:
46+
print(f"Agent deployment failed: {e}")
47+
print(f"Failed status: {e.status}")
48+
49+
except AgentDeploymentTimeoutError as e:
50+
print(f"Agent deployment timed out: {e}")
51+
print(f"Agent ID: {e.agent_id}")
52+
53+
except Exception as e:
54+
print(f"Unexpected error: {e}")
55+
56+
57+
# Async example
58+
from gradient import AsyncGradient
59+
60+
61+
async def main() -> None:
62+
async_client = AsyncGradient()
63+
64+
# Create a new agent
65+
agent_response = await async_client.agents.create(
66+
name="My Async Agent",
67+
instruction="You are a helpful assistant",
68+
model_uuid="<your-model-uuid>",
69+
region="nyc1",
70+
)
71+
72+
agent_id = agent_response.agent.uuid if agent_response.agent else None
73+
74+
if agent_id:
75+
print(f"Agent created with ID: {agent_id}")
76+
print("Waiting for agent to be ready...")
77+
78+
try:
79+
# Wait for the agent to be deployed and ready (async)
80+
ready_agent = await async_client.agents.wait_until_ready(
81+
agent_id,
82+
poll_interval=5.0,
83+
timeout=300.0,
84+
)
85+
86+
if ready_agent.agent and ready_agent.agent.deployment:
87+
print(f"Agent is ready! Status: {ready_agent.agent.deployment.status}")
88+
print(f"Agent URL: {ready_agent.agent.url}")
89+
90+
except AgentDeploymentError as e:
91+
print(f"Agent deployment failed: {e}")
92+
print(f"Failed status: {e.status}")
93+
94+
except AgentDeploymentTimeoutError as e:
95+
print(f"Agent deployment timed out: {e}")
96+
print(f"Agent ID: {e.agent_id}")
97+
98+
99+
# Uncomment to run async example
100+
# asyncio.run(main())

pyproject.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "gradient"
3-
version = "3.3.0"
3+
version = "3.6.0"
44
description = "The official Python library for the Gradient API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -117,11 +117,14 @@ replacement = '[\1](https://github.com/digitalocean/gradient-python/tree/main/\g
117117

118118
[tool.pytest.ini_options]
119119
testpaths = ["tests"]
120-
addopts = "--tb=short -n auto"
120+
addopts = "--tb=short -n auto -m 'not smoke'"
121121
xfail_strict = true
122122
asyncio_mode = "auto"
123123
asyncio_default_fixture_loop_scope = "session"
124124
filterwarnings = ["error"]
125+
markers = [
126+
"smoke: lightweight external integration smoke tests hitting live Gradient services",
127+
]
125128

126129
[tool.pyright]
127130
# this enables practically every flag given by pyright.
@@ -211,6 +214,8 @@ select = [
211214
"B",
212215
# remove unused imports
213216
"F401",
217+
# check for missing future annotations
218+
"FA102",
214219
# bare except statements
215220
"E722",
216221
# unused arguments
@@ -233,6 +238,8 @@ unfixable = [
233238
"T203",
234239
]
235240

241+
extend-safe-fixes = ["FA102"]
242+
236243
[tool.ruff.lint.flake8-tidy-imports.banned-api]
237244
"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead"
238245

pytest.ini

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/gradient/_base_client.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -548,14 +548,14 @@ def _build_request(
548548
# TODO: report this error to httpx
549549
return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
550550
headers=headers,
551-
timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
551+
timeout=(self.timeout if isinstance(options.timeout, NotGiven) else options.timeout),
552552
method=options.method,
553553
url=prepared_url,
554554
# the `Query` type that we use is incompatible with qs'
555555
# `Params` type as it needs to be typed as `Mapping[str, object]`
556556
# so that passing a `TypedDict` doesn't cause an error.
557557
# https://github.com/microsoft/pyright/issues/3526#event-6715453066
558-
params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
558+
params=(self.qs.stringify(cast(Mapping[str, Any], params)) if params else None),
559559
**kwargs,
560560
)
561561

@@ -1067,7 +1067,12 @@ def request(
10671067
)
10681068

10691069
def _sleep_for_retry(
1070-
self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
1070+
self,
1071+
*,
1072+
retries_taken: int,
1073+
max_retries: int,
1074+
options: FinalRequestOptions,
1075+
response: httpx.Response | None,
10711076
) -> None:
10721077
remaining_retries = max_retries - retries_taken
10731078
if remaining_retries == 1:
@@ -1248,7 +1253,11 @@ def post(
12481253
stream_cls: type[_StreamT] | None = None,
12491254
) -> ResponseT | _StreamT:
12501255
opts = FinalRequestOptions.construct(
1251-
method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1256+
method="post",
1257+
url=path,
1258+
json_data=body,
1259+
files=to_httpx_files(files),
1260+
**options,
12521261
)
12531262
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
12541263

@@ -1273,7 +1282,11 @@ def put(
12731282
options: RequestOptions = {},
12741283
) -> ResponseT:
12751284
opts = FinalRequestOptions.construct(
1276-
method="put", url=path, json_data=body, files=to_httpx_files(files), **options
1285+
method="put",
1286+
url=path,
1287+
json_data=body,
1288+
files=to_httpx_files(files),
1289+
**options,
12771290
)
12781291
return self.request(cast_to, opts)
12791292

@@ -1317,6 +1330,7 @@ def __init__(self, **kwargs: Any) -> None:
13171330
class _DefaultAioHttpClient(httpx.AsyncClient):
13181331
def __init__(self, **_kwargs: Any) -> None:
13191332
raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
1333+
13201334
else:
13211335

13221336
class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
@@ -1603,7 +1617,12 @@ async def request(
16031617
)
16041618

16051619
async def _sleep_for_retry(
1606-
self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
1620+
self,
1621+
*,
1622+
retries_taken: int,
1623+
max_retries: int,
1624+
options: FinalRequestOptions,
1625+
response: httpx.Response | None,
16071626
) -> None:
16081627
remaining_retries = max_retries - retries_taken
16091628
if remaining_retries == 1:
@@ -1772,7 +1791,11 @@ async def post(
17721791
stream_cls: type[_AsyncStreamT] | None = None,
17731792
) -> ResponseT | _AsyncStreamT:
17741793
opts = FinalRequestOptions.construct(
1775-
method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1794+
method="post",
1795+
url=path,
1796+
json_data=body,
1797+
files=await async_to_httpx_files(files),
1798+
**options,
17761799
)
17771800
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
17781801

@@ -1797,7 +1820,11 @@ async def put(
17971820
options: RequestOptions = {},
17981821
) -> ResponseT:
17991822
opts = FinalRequestOptions.construct(
1800-
method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1823+
method="put",
1824+
url=path,
1825+
json_data=body,
1826+
files=await async_to_httpx_files(files),
1827+
**options,
18011828
)
18021829
return await self.request(cast_to, opts)
18031830

0 commit comments

Comments
 (0)