Skip to content

Commit 00bbca2

Browse files
committed
refactor!: Adapt to apify-client v3
Build the SDK on apify-client v3 and drop the apify-shared dependency: typed model responses (Run, pricing info, webhook representations), Literal string aliases instead of StrEnum classes, the new tiered timeout system, and a slimmed-down @DataClass Webhook. Collapse the SDK's standalone pricing-info models into thin subclasses of the apify-client models that relax only the fields the platform's APIFY_ACTOR_PRICING_INFO env var omits, so Run.pricing_info from the API flows through unchanged and the converter is removed. Configuration.actor_pricing_info keeps its discriminated-union shape (no public API change), and event_price_usd is now correctly optional so tier-priced pay-per-event Actors no longer fail env-var validation. Document these changes in the v4 upgrading guide.
1 parent 24a6edb commit 00bbca2

51 files changed

Lines changed: 1385 additions & 823 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/02_concepts/code/07_webhook.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import asyncio
22

3-
from apify import Actor, Webhook, WebhookEventType
3+
from apify import Actor, Webhook
44

55

66
async def main() -> None:
77
async with Actor:
88
# Create a webhook that will be triggered when the Actor run fails.
99
webhook = Webhook(
10-
event_types=[WebhookEventType.ACTOR_RUN_FAILED],
10+
event_types=['ACTOR.RUN.FAILED'],
1111
request_url='https://example.com/run-failed',
1212
)
1313

docs/02_concepts/code/07_webhook_preventing.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import asyncio
22

3-
from apify import Actor, Webhook, WebhookEventType
3+
from apify import Actor, Webhook
44

55

66
async def main() -> None:
77
async with Actor:
8-
# Create a webhook that will be triggered when the Actor run fails.
8+
# Create a webhook with an idempotency key to prevent duplicates on retries.
99
webhook = Webhook(
10-
event_types=[WebhookEventType.ACTOR_RUN_FAILED],
10+
event_types=['ACTOR.RUN.FAILED'],
1111
request_url='https://example.com/run-failed',
12+
idempotency_key=Actor.configuration.actor_run_id,
1213
)
1314

1415
# Add the webhook to the Actor.
15-
await Actor.add_webhook(webhook, idempotency_key=Actor.configuration.actor_run_id)
16+
await Actor.add_webhook(webhook)
1617

1718
# Raise an error to simulate a failed run.
1819
raise RuntimeError('I am an error and I know it!')

docs/04_upgrading/upgrading_to_v4.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,137 @@ run = await Actor.call('user/actor', timeout='inherit')
5050
The deprecated `latest_sdk_version`, `log_format`, and `standby_port` fields have been removed from `Configuration`:
5151
- In place of `standby_port`, use `web_server_port`.
5252
- `latest_sdk_version` and `log_format` don't have replacement. SDK version checking isn't supported for the Python SDK and the log format should be adjusted in code instead.
53+
54+
## Built on `apify-client` v3
55+
56+
The SDK is now built on [`apify-client`](https://docs.apify.com/api/client/python) v3 and no longer depends on `apify-shared`. The sections below cover the user-visible consequences; see the client's [Upgrading to v3](https://docs.apify.com/api/client/python/docs/upgrading/upgrading-to-v3) guide for the full list of changes in the client itself.
57+
58+
## Typed responses
59+
60+
`Actor.start`, `Actor.abort`, `Actor.call`, and `Actor.call_task` now return `apify_client._models.Run` instead of the SDK-side `ActorRun`. Both are [Pydantic](https://docs.pydantic.dev/latest/) models with the same snake_case fields, so field access is unchanged — only the type and import path differ. The SDK no longer ships its own response models (`apify._models` has been removed); response shapes come from `apify-client`.
61+
62+
## Literal string aliases instead of StrEnum classes
63+
64+
Generated enum-like types are now [`Literal`](https://docs.python.org/3/library/typing.html#typing.Literal) string aliases instead of `StrEnum` classes. Pass plain strings instead of enum members.
65+
66+
- `apify.WebhookEventType` is now a `Literal[...]` instead of a `StrEnum`. Use plain string values (`'ACTOR.RUN.FAILED'`) instead of enum members.
67+
- `apify_shared.consts.ActorEventTypes` (a `StrEnum`) is replaced by `apify.ActorEventTypes`, now a `Literal['systemInfo', 'persistState', 'migrating', 'aborting']`. For runtime values, use `apify.Event` (re-exported from Crawlee) instead of enum members.
68+
69+
**Before (v3.x):**
70+
71+
```python
72+
from apify import Actor
73+
from apify_shared.consts import ActorEventTypes
74+
75+
Actor.on(ActorEventTypes.SYSTEM_INFO, callback)
76+
```
77+
78+
**Now (v4.0):**
79+
80+
```python
81+
from apify import Actor, Event
82+
83+
Actor.on(Event.SYSTEM_INFO, callback)
84+
```
85+
86+
## Actor pricing info models
87+
88+
The Actor pricing-info models exposed through `Actor.configuration.actor_pricing_info``FreeActorPricingInfo`, `FlatPricePerMonthActorPricingInfo`, `PricePerDatasetItemActorPricingInfo`, `PayPerEventActorPricingInfo`, and the nested `ActorChargeEvent` / `PricingPerEvent` — are now thin subclasses of the corresponding `apify-client` models instead of standalone SDK copies. The discriminated-union shape is unchanged, so existing access (`pricing_model`, per-event titles and prices) keeps working; the models now expose the full `apify-client` field set, and a charge event's `event_price_usd` is optional (it is unset for tier-priced events). `ChargingManager.get_pricing_info()` is unchanged.
89+
90+
## `Webhook` API simplified
91+
92+
The `Webhook` model has been slimmed down to only the fields a user sets when defining a webhook. Server-populated response fields (`id`, `created_at`, `modified_at`, `user_id`, `is_ad_hoc`, `condition`, `last_dispatch`, `stats`) and the unused `WebhookCondition` helper class have been removed. `Webhook` is now a plain `@dataclass` instead of a Pydantic `BaseModel` — construct it with snake_case kwargs; `.model_dump()` / `.model_validate()` are gone.
93+
94+
The retry and idempotency kwargs that used to live on `Actor.add_webhook` have moved onto the `Webhook` instance itself.
95+
96+
**Before (v3.x):**
97+
98+
```python
99+
from apify import Actor, Webhook
100+
101+
await Actor.add_webhook(
102+
Webhook(event_types=['ACTOR.RUN.FAILED'], request_url='https://example.com'),
103+
ignore_ssl_errors=False,
104+
do_not_retry=False,
105+
idempotency_key='my-key',
106+
)
107+
```
108+
109+
**Now (v4.0):**
110+
111+
```python
112+
from apify import Actor, Webhook
113+
114+
await Actor.add_webhook(
115+
Webhook(
116+
event_types=['ACTOR.RUN.FAILED'],
117+
request_url='https://example.com',
118+
ignore_ssl_errors=False,
119+
do_not_retry=False,
120+
idempotency_key='my-key',
121+
)
122+
)
123+
```
124+
125+
The `idempotency_key` kwarg form on `Actor.add_webhook` still works for one more release but emits a `DeprecationWarning` and will be removed in v5.0. The `ignore_ssl_errors` and `do_not_retry` kwargs have been removed outright — set them on the `Webhook` instance.
126+
127+
`apify.WebhookCondition` is no longer exported; the SDK now binds the webhook to the current Actor run internally.
128+
129+
The `webhooks` argument on `Actor.start`, `Actor.call`, and `Actor.call_task` still accepts `list[Webhook]` and the fields used at the call site (`event_types`, `request_url`, `payload_template`, `headers_template`) are unchanged.
130+
131+
## `Actor.new_client``timeout` scales all tiers
132+
133+
`apify-client` v3 split its single timeout into four tiers (short / medium / long / max). `Actor.new_client(timeout=...)` still takes a single `timedelta`; the SDK uses it as the medium-tier baseline and scales the other tiers proportionally (short = `timeout / 6`, long = `timeout * 12`, max = `timeout * 24`). The public signature is unchanged — no migration needed.
134+
135+
## Using the client from `Actor.new_client`
136+
137+
`Actor.new_client()` (and the `Actor.apify_client` property) now returns an `apify-client` v3 `ApifyClientAsync`. When you use that client directly, the client's v3 breaking changes apply — the most impactful ones are below. See the client's [Upgrading to v3](https://docs.apify.com/api/client/python/docs/upgrading/upgrading-to-v3) guide for the complete reference.
138+
139+
### 404 raises `NotFoundError` on ambiguous endpoints
140+
141+
Direct `.get(id)` and `.delete(id)` calls still swallow 404 into `None`. But where a 404 could mean either the parent or the sub-resource is missing, the client now raises `NotFoundError` instead of returning `None`.
142+
143+
**Before (v3.x):**
144+
145+
```python
146+
client = Actor.new_client()
147+
148+
# Returned None on 404.
149+
dataset = await client.run('some-run-id').dataset().get()
150+
```
151+
152+
**Now (v4.0):**
153+
154+
```python
155+
from apify_client.errors import NotFoundError
156+
157+
client = Actor.new_client()
158+
159+
# Raises NotFoundError; handle it explicitly.
160+
try:
161+
dataset = await client.run('some-run-id').dataset().get()
162+
except NotFoundError:
163+
dataset = None
164+
```
165+
166+
### Keyword-only arguments
167+
168+
Secondary parameters on several client methods can no longer be passed positionally.
169+
170+
**Before (v3.x):**
171+
172+
```python
173+
await client.key_value_store('my-store').set_record('my-key', {'data': 1}, 'application/json')
174+
await client.run('my-run').charge('my-event', 5)
175+
```
176+
177+
**Now (v4.0):**
178+
179+
```python
180+
await client.key_value_store('my-store').set_record('my-key', {'data': 1}, content_type='application/json')
181+
await client.run('my-run').charge('my-event', count=5)
182+
```
183+
184+
### Async `iterate_*` are no longer coroutine functions
185+
186+
`DatasetClientAsync.iterate_items()` and `KeyValueStoreClientAsync.iterate_keys()` are now plain `def` functions returning `AsyncIterator[T]`. Consumer code (`async for ...`) is unchanged; if you annotate the call's return value, change `AsyncGenerator[T, None]` to `AsyncIterator[T]`.

pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@ keywords = [
3434
"scraping",
3535
]
3636
dependencies = [
37-
"apify-client>=2.3.0,<3.0.0",
38-
"apify-shared>=2.0.0,<3.0.0",
37+
"apify-client>=3.0.0,<4.0.0",
3938
"crawlee>=1.0.4,<2.0.0",
4039
"cachetools>=5.5.0",
4140
"cryptography>=42.0.0",
4241
"impit>=0.8.0",
4342
"lazy-object-proxy>=1.11.0",
4443
"more_itertools>=10.2.0",
45-
"pydantic>=2.11.0",
44+
"pydantic[email]>=2.11.0",
4645
"typing-extensions>=4.1.0",
4746
"websockets>=14.0",
4847
"yarl>=1.18.0",
@@ -197,7 +196,7 @@ builtins-ignorelist = ["id"]
197196

198197
[tool.ruff.lint.isort]
199198
known-local-folder = ["apify"]
200-
known-first-party = ["apify_client", "apify_shared", "crawlee"]
199+
known-first-party = ["apify_client", "crawlee"]
201200

202201
[tool.ruff.lint.pylint]
203202
max-branches = 18

src/apify/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from importlib import metadata
22

3-
from apify_shared.consts import WebhookEventType
3+
from apify_client._literals import WebhookEventType
44
from crawlee import Request
55
from crawlee.events import (
66
Event,
@@ -14,13 +14,15 @@
1414

1515
from apify._actor import Actor
1616
from apify._configuration import Configuration
17-
from apify._models import Webhook
1817
from apify._proxy_configuration import ProxyConfiguration, ProxyInfo
18+
from apify._webhook import Webhook
19+
from apify.events._types import ActorEventTypes
1920

2021
__version__ = metadata.version('apify')
2122

2223
__all__ = [
2324
'Actor',
25+
'ActorEventTypes',
2426
'Configuration',
2527
'Event',
2628
'EventAbortingData',

0 commit comments

Comments
 (0)