Skip to content

Commit cb76ee2

Browse files
vdusekclaude
andauthored
docs: Expand v3 upgrade guide to cover all breaking changes (#698)
## Summary - Expanded the v3 upgrade guide to document all 6 breaking changes from the changelog: Python version support, fully typed clients (Pydantic models for responses and parameters), pluggable HTTP client architecture, and tiered timeout system. - Converted both upgrading guides (`upgrading_to_v2`, `upgrading_to_v3`) from `.md` to `.mdx` and replaced plain markdown API references with `ApiLink` components for consistent linking to the API reference docs. - Fixed the guide link to use the correct `custom-http-client-httpx` id. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ad53091 commit cb76ee2

File tree

4 files changed

+192
-13
lines changed

4 files changed

+192
-13
lines changed

docs/04_upgrading/upgrading_to_v2.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Several public methods have changed their signatures or behavior.
2727

2828
### KeyValueStoreClient
2929

30-
- The deprecated parameters `as_bytes` and `as_file` have been removed from `KeyValueStoreClient.get_record()`. Use the dedicated methods `get_record_as_bytes()` and `stream_record()` instead.
30+
- The deprecated parameters `as_bytes` and `as_file` have been removed from <ApiLink to="class/KeyValueStoreClient#get_record">`KeyValueStoreClient.get_record()`</ApiLink>. Use the dedicated methods <ApiLink to="class/KeyValueStoreClient#get_record_as_bytes">`get_record_as_bytes()`</ApiLink> and <ApiLink to="class/KeyValueStoreClient#stream_record">`stream_record()`</ApiLink> instead.
3131

3232
### DatasetClient
3333

docs/04_upgrading/upgrading_to_v3.md

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
id: upgrading-to-v3
3+
title: Upgrading to v3
4+
description: Breaking changes and migration guide from v2 to v3.
5+
---
6+
7+
import ApiLink from '@site/src/components/ApiLink';
8+
9+
This page summarizes the breaking changes between Apify Python API Client v2.x and v3.0.
10+
11+
## Python version support
12+
13+
Support for Python 3.10 has been dropped. The Apify Python API Client v3.x now requires Python 3.11 or later. Make sure your environment is running a compatible version before upgrading.
14+
15+
## Fully typed clients
16+
17+
Resource client methods now return [Pydantic](https://docs.pydantic.dev/latest/) models instead of plain dictionaries. This provides IDE autocompletion, type checking, and early validation of API responses.
18+
19+
### Accessing response fields
20+
21+
Before (v2):
22+
23+
```python
24+
from apify_client import ApifyClient
25+
26+
client = ApifyClient(token='MY-APIFY-TOKEN')
27+
28+
# v2 — methods returned plain dicts
29+
run = client.actor('apify/hello-world').call(run_input={'key': 'value'})
30+
dataset_id = run['defaultDatasetId']
31+
status = run['status']
32+
```
33+
34+
After (v3):
35+
36+
```python
37+
from apify_client import ApifyClient
38+
39+
client = ApifyClient(token='MY-APIFY-TOKEN')
40+
41+
# v3 — methods return Pydantic models
42+
run = client.actor('apify/hello-world').call(run_input={'key': 'value'})
43+
dataset_id = run.default_dataset_id
44+
status = run.status
45+
```
46+
47+
All model classes are generated from the Apify OpenAPI specification and live in `apify_client._models` module. They are configured with `extra='allow'`, so any new fields added to the API in the future are preserved on the model instance. Fields are accessed using their Python snake_case names:
48+
49+
```python
50+
run.default_dataset_id # ✓ use snake_case attribute names
51+
run.id
52+
run.status
53+
```
54+
55+
Models also use `populate_by_name=True`, which means you can use either the Python field name or the camelCase alias when **constructing** a model:
56+
57+
```python
58+
from apify_client._models import Run
59+
60+
# Both work when constructing models
61+
Run(default_dataset_id='abc') # Python field name
62+
Run(defaultDatasetId='abc') # camelCase API alias
63+
```
64+
65+
### Exceptions
66+
67+
Not every method returns a Pydantic model. Methods whose payloads are user-defined or inherently unstructured still return plain types:
68+
69+
- <ApiLink to="class/DatasetClient#list_items">`DatasetClient.list_items()`</ApiLink> returns `DatasetItemsPage`, a dataclass whose `items` field is `list[dict[str, Any]]`, because the structure of dataset items is defined by the [Actor output schema](https://docs.apify.com/platform/actors/development/actor-definition/output-schema), which the API Client or SDK has no knowledge of.
70+
- <ApiLink to="class/KeyValueStoreClient#get_record">`KeyValueStoreClient.get_record()`</ApiLink> returns a `dict` with `key`, `value`, and `content_type` keys.
71+
72+
### Pydantic models as method parameters
73+
74+
Resource client methods that previously accepted only dictionaries for structured input now also accept Pydantic models. Existing code that passes dictionaries continues to work — this change is additive for callers, but is listed here because method type signatures have changed.
75+
76+
Before (v2):
77+
78+
```python
79+
rq_client.add_request({
80+
'url': 'https://example.com',
81+
'uniqueKey': 'https://example.com',
82+
'method': 'GET',
83+
})
84+
```
85+
86+
After (v3) — both forms are accepted:
87+
88+
```python
89+
from apify_client._types import RequestInput
90+
91+
# Option 1: dict (still works)
92+
rq_client.add_request({
93+
'url': 'https://example.com',
94+
'uniqueKey': 'https://example.com',
95+
'method': 'GET',
96+
})
97+
98+
# Option 2: Pydantic model (new)
99+
rq_client.add_request(RequestInput(
100+
url='https://example.com',
101+
unique_key='https://example.com',
102+
method='GET',
103+
))
104+
```
105+
106+
Model input is available on methods such as <ApiLink to="class/RequestQueueClient#add_request">`RequestQueueClient.add_request()`</ApiLink>, <ApiLink to="class/RequestQueueClient#batch_add_requests">`RequestQueueClient.batch_add_requests()`</ApiLink>, <ApiLink to="class/ActorClient#start">`ActorClient.start()`</ApiLink>, <ApiLink to="class/ActorClient#call">`ActorClient.call()`</ApiLink>, <ApiLink to="class/TaskClient#start">`TaskClient.start()`</ApiLink>, <ApiLink to="class/TaskClient#call">`TaskClient.call()`</ApiLink>, <ApiLink to="class/TaskClient#update">`TaskClient.update()`</ApiLink>, and <ApiLink to="class/TaskClient#update_input">`TaskClient.update_input()`</ApiLink>, among others. Check the API reference for the complete list.
107+
108+
## Pluggable HTTP client architecture
109+
110+
The HTTP layer is now abstracted behind <ApiLink to="class/HttpClient">`HttpClient`</ApiLink> and <ApiLink to="class/HttpClientAsync">`HttpClientAsync`</ApiLink> base classes. The default implementation based on [Impit](https://github.com/apify/impit) (<ApiLink to="class/ImpitHttpClient">`ImpitHttpClient`</ApiLink> / <ApiLink to="class/ImpitHttpClientAsync">`ImpitHttpClientAsync`</ApiLink>) is unchanged, but you can now replace it with your own.
111+
112+
To use a custom HTTP client, implement the `call()` method and pass the instance via the <ApiLink to="class/ApifyClient#with_custom_http_client">`ApifyClient.with_custom_http_client()`</ApiLink> class method:
113+
114+
```python
115+
from apify_client import ApifyClient, HttpClient, HttpResponse, Timeout
116+
117+
class MyHttpClient(HttpClient):
118+
def call(self, *, method, url, headers=None, params=None,
119+
data=None, json=None, stream=None, timeout='medium') -> HttpResponse:
120+
...
121+
122+
client = ApifyClient.with_custom_http_client(
123+
token='MY-APIFY-TOKEN',
124+
http_client=MyHttpClient(),
125+
)
126+
```
127+
128+
The response must satisfy the <ApiLink to="class/HttpResponse">`HttpResponse`</ApiLink> protocol (properties: `status_code`, `text`, `content`, `headers`; methods: `json()`, `read()`, `close()`, `iter_bytes()`). Many popular libraries like `httpx` already satisfy this protocol out of the box.
129+
130+
For a full walkthrough and working examples, see the [Custom HTTP clients](/docs/concepts/custom-http-clients) concept page and the [Custom HTTP client](/docs/guides/custom-http-client-httpx) guide.
131+
132+
## Tiered timeout system
133+
134+
Individual API methods now use a tiered timeout instead of a single global timeout. Each method declares a default tier appropriate for its expected latency.
135+
136+
### Timeout tiers
137+
138+
| Tier | Default | Typical use case |
139+
|---|---|---|
140+
| `short` | 5 s | Fast CRUD operations (get, update, delete) |
141+
| `medium` | 30 s | Batch and list operations, starting runs |
142+
| `long` | 360 s | Long-polling, streaming, data retrieval |
143+
| `no_timeout` | Disabled | Blocking calls like `actor.call()` that wait for a run to finish |
144+
145+
A `timeout_max` value (default 360 s) caps the exponential growth of timeouts across retries.
146+
147+
### Configuring default tiers
148+
149+
You can override the default duration of any tier on the <ApiLink to="class/ApifyClient">`ApifyClient`</ApiLink> constructor:
150+
151+
```python
152+
from datetime import timedelta
153+
154+
from apify_client import ApifyClient
155+
156+
client = ApifyClient(
157+
token='MY-APIFY-TOKEN',
158+
timeout_short=timedelta(seconds=10),
159+
timeout_medium=timedelta(seconds=60),
160+
timeout_long=timedelta(seconds=600),
161+
timeout_max=timedelta(seconds=600),
162+
)
163+
```
164+
165+
### Per-call override
166+
167+
Every resource client method exposes a `timeout` parameter. You can pass a tier name or a `timedelta` for a one-off override:
168+
169+
```python
170+
from datetime import timedelta
171+
172+
# Use the 'long' tier for this specific call
173+
actor = client.actor('apify/hello-world').get(timeout='long')
174+
175+
# Or pass an explicit duration
176+
actor = client.actor('apify/hello-world').get(timeout=timedelta(seconds=120))
177+
```
178+
179+
### Retry behavior
180+
181+
On retries, the timeout doubles with each attempt (exponential backoff) up to `timeout_max`. For example, with `timeout_short=5s` and `timeout_max=360s`: attempt 1 uses 5 s, attempt 2 uses 10 s, attempt 3 uses 20 s, and so on.
182+
183+
### Updated default timeout tiers
184+
185+
The default timeout tier assigned to each method on non-storage resource clients has been revised to better match the expected latency of the underlying API endpoint. For example, a simple `get()` call now defaults to `short` (5 s), while `start()` defaults to `medium` (30 s) and `call()` defaults to `no_timeout`.
186+
187+
If your code relied on the previous global timeout behavior, review the timeout tier on the methods you use and adjust via the `timeout` parameter or by overriding tier defaults on the <ApiLink to="class/ApifyClient">`ApifyClient`</ApiLink> constructor (see [Tiered timeout system](#tiered-timeout-system) above).

website/versioned_docs/version-2.5/04_upgrading/upgrading_to_v2.md renamed to website/versioned_docs/version-2.5/04_upgrading/upgrading_to_v2.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
---
22
id: upgrading-to-v2
33
title: Upgrading to v2
4+
description: Breaking changes and migration guide from v1 to v2.
45
---
56

7+
import ApiLink from '@site/src/components/ApiLink';
8+
69
This page summarizes the breaking changes between Apify Python API Client v1.x and v2.0.
710

811
## Python version support
@@ -24,7 +27,7 @@ Several public methods have changed their signatures or behavior.
2427

2528
### KeyValueStoreClient
2629

27-
- The deprecated parameters `as_bytes` and `as_file` have been removed from `KeyValueStoreClient.get_record()`. Use the dedicated methods `get_record_as_bytes()` and `stream_record()` instead.
30+
- The deprecated parameters `as_bytes` and `as_file` have been removed from <ApiLink to="class/KeyValueStoreClient#get_record">`KeyValueStoreClient.get_record()`</ApiLink>. Use the dedicated methods <ApiLink to="class/KeyValueStoreClient#get_record_as_bytes">`get_record_as_bytes()`</ApiLink> and <ApiLink to="class/KeyValueStoreClient#stream_record">`stream_record()`</ApiLink> instead.
2831

2932
### DatasetClient
3033

0 commit comments

Comments
 (0)