Skip to content

Commit 3e5728d

Browse files
authored
refactor!: remove deprecated APIs (#918)
Removes deprecated APIs as part of the v4 major release. ### `api_public_base_url` argument of storage clients (closes #635) Removed the deprecated `api_public_base_url` `__init__` argument from `ApifyDatasetClient` and `ApifyKeyValueStoreClient` (and the `open()` call sites that passed `''`). It had no effect already in v3 — passing it only emitted a `DeprecationWarning`. The public base URL is taken from `Configuration.api_public_base_url`, which is unchanged. ### `RemainingTime` timeout literal Removed the deprecated `'RemainingTime'` value of the `timeout` argument from `Actor.start()` and `Actor.call()`. Use `'inherit'` instead — the behavior is identical. ### Deprecated `Configuration` fields Removed the `@deprecated` config fields `latest_sdk_version`, `log_format`, and `standby_port` (use `web_server_port` instead). Also drops the now-unused `warnings`/`deprecated` imports and the obsolete deprecation-warning unit tests, and documents all removals in the v4 upgrade guide. Mirrors apify/apify-client-python#799 for this repository.
1 parent d2eec27 commit 3e5728d

8 files changed

Lines changed: 54 additions & 118 deletions

File tree

docs/04_upgrading/upgrading_to_v4.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,44 @@ This guide lists the breaking changes between Apify Python SDK v3.x and v4.0.
99
## Python 3.11+ required
1010

1111
Support for Python 3.10 has been dropped. The Apify Python SDK v4.x now requires Python 3.11 or later — make sure your environment is on a compatible version before upgrading.
12+
13+
## Removal of deprecated APIs
14+
15+
Methods and arguments that had been deprecated in v3 are removed in v4.
16+
17+
### api_public_base_url argument of storage clients
18+
19+
The deprecated `api_public_base_url` argument has been removed from `ApifyDatasetClient` and `ApifyKeyValueStoreClient`. It had no effect already in v3, passing it emitted only a `DeprecationWarning`. Drop it from your call sites. The public base URL is taken from `Configuration.api_public_base_url`, which is unchanged.
20+
21+
```python
22+
# Before (v3)
23+
client = ApifyDatasetClient(
24+
api_client=api_client,
25+
api_public_base_url='https://api.apify.com',
26+
lock=lock,
27+
)
28+
29+
# After (v4)
30+
client = ApifyDatasetClient(
31+
api_client=api_client,
32+
lock=lock,
33+
)
34+
```
35+
36+
### Actor.start and Actor.call: RemainingTime
37+
38+
The deprecated `RemainingTime` value of the `timeout` argument has been removed from `Actor.start()` and `Actor.call()`. Use `inherit` instead, the signature and behavior are identical.
39+
40+
```python
41+
# Before (v3)
42+
run = await Actor.call('user/actor', timeout='RemainingTime')
43+
44+
# After (v4)
45+
run = await Actor.call('user/actor', timeout='inherit')
46+
```
47+
48+
### Deprecated Configuration fields
49+
50+
The deprecated `latest_sdk_version`, `log_format`, and `standby_port` fields have been removed from `Configuration`:
51+
- In place of `standby_port`, use `web_server_port`.
52+
- `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.

src/apify/_actor.py

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import asyncio
44
import sys
5-
import warnings
65
from contextlib import suppress
76
from datetime import UTC, datetime, timedelta
87
from functools import cached_property
@@ -867,7 +866,7 @@ async def start(
867866
max_total_charge_usd: Decimal | None = None,
868867
restart_on_error: bool | None = None,
869868
memory_mbytes: int | None = None,
870-
timeout: timedelta | None | Literal['inherit', 'RemainingTime'] = None,
869+
timeout: timedelta | None | Literal['inherit'] = None,
871870
force_permission_level: ActorPermissionLevel | None = None,
872871
wait_for_finish: int | None = None,
873872
webhooks: list[Webhook] | None = None,
@@ -889,8 +888,8 @@ async def start(
889888
memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
890889
in the default run configuration for the Actor.
891890
timeout: Optional timeout for the run, in seconds. By default, the run uses timeout specified in
892-
the default run configuration for the Actor. Using `inherit` or `RemainingTime` will set timeout of the
893-
other Actor to the time remaining from this Actor timeout.
891+
the default run configuration for the Actor. Using `inherit` will set timeout of the other Actor
892+
to the time remaining from this Actor timeout.
894893
force_permission_level: Override the Actor's permissions for this run. If not set, the Actor will run
895894
with permissions configured in the Actor settings.
896895
wait_for_finish: The maximum number of seconds the server waits for the run to finish. By default,
@@ -911,22 +910,14 @@ async def start(
911910
else:
912911
serialized_webhooks = None
913912

914-
if timeout in {'inherit', 'RemainingTime'}:
915-
if timeout == 'RemainingTime':
916-
warnings.warn(
917-
'`RemainingTime` is deprecated and will be removed in version 4.0.0. Use `inherit` instead.',
918-
DeprecationWarning,
919-
stacklevel=2,
920-
)
913+
if timeout == 'inherit':
921914
actor_start_timeout = self._get_remaining_time()
922915
elif timeout is None:
923916
actor_start_timeout = None
924917
elif isinstance(timeout, timedelta):
925918
actor_start_timeout = timeout
926919
else:
927-
raise ValueError(
928-
f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, `"RemainingTime"`, or a `timedelta`.'
929-
)
920+
raise ValueError(f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, or a `timedelta`.')
930921

931922
api_result = await client.actor(actor_id).start(
932923
run_input=run_input,
@@ -988,7 +979,7 @@ async def call(
988979
max_total_charge_usd: Decimal | None = None,
989980
restart_on_error: bool | None = None,
990981
memory_mbytes: int | None = None,
991-
timeout: timedelta | None | Literal['inherit', 'RemainingTime'] = None,
982+
timeout: timedelta | None | Literal['inherit'] = None,
992983
force_permission_level: ActorPermissionLevel | None = None,
993984
webhooks: list[Webhook] | None = None,
994985
wait: timedelta | None = None,
@@ -1011,8 +1002,8 @@ async def call(
10111002
memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
10121003
in the default run configuration for the Actor.
10131004
timeout: Optional timeout for the run, in seconds. By default, the run uses timeout specified in
1014-
the default run configuration for the Actor. Using `inherit` or `RemainingTime` will set timeout of the
1015-
other Actor to the time remaining from this Actor timeout.
1005+
the default run configuration for the Actor. Using `inherit` will set timeout of the other Actor
1006+
to the time remaining from this Actor timeout.
10161007
force_permission_level: Override the Actor's permissions for this run. If not set, the Actor will run
10171008
with permissions configured in the Actor settings.
10181009
webhooks: Optional webhooks (https://docs.apify.com/webhooks) associated with the Actor run, which can
@@ -1036,23 +1027,14 @@ async def call(
10361027
else:
10371028
serialized_webhooks = None
10381029

1039-
if timeout in {'inherit', 'RemainingTime'}:
1040-
if timeout == 'RemainingTime':
1041-
warnings.warn(
1042-
'`RemainingTime` is deprecated and will be removed in version 4.0.0. Use `inherit` instead.',
1043-
DeprecationWarning,
1044-
stacklevel=2,
1045-
)
1046-
1030+
if timeout == 'inherit':
10471031
actor_call_timeout = self._get_remaining_time()
10481032
elif timeout is None:
10491033
actor_call_timeout = None
10501034
elif isinstance(timeout, timedelta):
10511035
actor_call_timeout = timeout
10521036
else:
1053-
raise ValueError(
1054-
f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, `"RemainingTime"`, or a `timedelta`.'
1055-
)
1037+
raise ValueError(f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, or a `timedelta`.')
10561038

10571039
api_result = await client.actor(actor_id).call(
10581040
run_input=run_input,
@@ -1450,7 +1432,7 @@ def _get_remaining_time(self) -> timedelta | None:
14501432
return max(self.configuration.timeout_at - datetime.now(tz=UTC), timedelta(0))
14511433

14521434
self.log.warning(
1453-
'Using `inherit` or `RemainingTime` argument is only possible when the Actor'
1435+
'Using `inherit` argument is only possible when the Actor'
14541436
' is running on the Apify platform and when the timeout for the Actor run is set. '
14551437
f'{self.is_at_home()=}, {self.configuration.timeout_at=}'
14561438
)

src/apify/_configuration.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Annotated, Any, Self
99

1010
from pydantic import AliasChoices, BeforeValidator, Field, model_validator
11-
from typing_extensions import TypedDict, deprecated
11+
from typing_extensions import TypedDict
1212

1313
from crawlee import service_locator
1414
from crawlee._utils.models import timedelta_ms
@@ -273,22 +273,6 @@ class Configuration(CrawleeConfiguration):
273273
),
274274
] = False
275275

276-
latest_sdk_version: Annotated[
277-
str | None,
278-
Field(
279-
alias='apify_sdk_latest_version',
280-
description='Specifies the most recent release version of the Apify SDK for Javascript. Used for '
281-
'checking for updates.',
282-
),
283-
deprecated('SDK version checking is not supported for the Python SDK'),
284-
] = None
285-
286-
log_format: Annotated[
287-
str | None,
288-
Field(alias='apify_log_format'),
289-
deprecated('Adjust the log format in code instead'),
290-
] = None
291-
292276
max_paid_dataset_items: Annotated[
293277
int | None,
294278
Field(
@@ -386,15 +370,6 @@ class Configuration(CrawleeConfiguration):
386370
BeforeValidator(lambda val: val if val != '' else None), # We should accept empty environment variables as well
387371
] = None
388372

389-
standby_port: Annotated[
390-
int,
391-
Field(
392-
alias='actor_standby_port',
393-
description='TCP port for the Actor to start an HTTP server to receive messages in the Actor Standby mode',
394-
),
395-
deprecated('Use `web_server_port` instead'),
396-
] = 4321
397-
398373
standby_url: Annotated[
399374
str,
400375
BeforeValidator(validate_http_url),

src/apify/storage_clients/_apify/_dataset_client.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import asyncio
4-
import warnings
54
from logging import getLogger
65
from typing import TYPE_CHECKING
76

@@ -42,7 +41,6 @@ def __init__(
4241
self,
4342
*,
4443
api_client: DatasetClientAsync,
45-
api_public_base_url: str,
4644
lock: asyncio.Lock,
4745
) -> None:
4846
"""Initialize a new instance.
@@ -58,14 +56,6 @@ def __init__(
5856
self._lock = lock
5957
"""A lock to ensure that only one operation is performed at a time."""
6058

61-
if api_public_base_url:
62-
# Remove in version 4.0, https://github.com/apify/apify-sdk-python/issues/635
63-
warnings.warn(
64-
'api_public_base_url argument is deprecated and will be removed in version 4.0.0',
65-
DeprecationWarning,
66-
stacklevel=2,
67-
)
68-
6959
@override
7060
async def get_metadata(self) -> DatasetMetadata:
7161
metadata = await self._api_client.get()
@@ -114,7 +104,6 @@ async def open(
114104

115105
dataset_client = cls(
116106
api_client=api_client,
117-
api_public_base_url='', # Remove in version 4.0, https://github.com/apify/apify-sdk-python/issues/635
118107
lock=asyncio.Lock(),
119108
)
120109

src/apify/storage_clients/_apify/_key_value_store_client.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import asyncio
4-
import warnings
54
from logging import getLogger
65
from typing import TYPE_CHECKING, Any
76

@@ -30,7 +29,6 @@ def __init__(
3029
self,
3130
*,
3231
api_client: KeyValueStoreClientAsync,
33-
api_public_base_url: str,
3432
lock: asyncio.Lock,
3533
) -> None:
3634
"""Initialize a new instance.
@@ -43,14 +41,6 @@ def __init__(
4341
self._lock = lock
4442
"""A lock to ensure that only one operation is performed at a time."""
4543

46-
if api_public_base_url:
47-
# Remove in version 4.0, https://github.com/apify/apify-sdk-python/issues/635
48-
warnings.warn(
49-
'api_public_base_url argument is deprecated and will be removed in version 4.0.0',
50-
DeprecationWarning,
51-
stacklevel=2,
52-
)
53-
5444
@override
5545
async def get_metadata(self) -> ApifyKeyValueStoreMetadata:
5646
metadata = await self._api_client.get()
@@ -98,7 +88,6 @@ async def open(
9888
)
9989
return cls(
10090
api_client=api_client,
101-
api_public_base_url='', # Remove in version 4.0, https://github.com/apify/apify-sdk-python/issues/635
10291
lock=asyncio.Lock(),
10392
)
10493

tests/unit/actor/test_actor_helpers.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import asyncio
44
import logging
5-
import warnings
65
from datetime import UTC, datetime, timedelta
76
from typing import TYPE_CHECKING
87

@@ -267,21 +266,6 @@ async def test_remote_method_with_timedelta_timeout(
267266
assert kwargs.get('timeout_secs') == 120
268267

269268

270-
async def test_call_actor_with_remaining_time_deprecation(
271-
apify_client_async_patcher: ApifyClientAsyncPatcher, fake_actor_run: dict
272-
) -> None:
273-
"""Test that call() with RemainingTime emits deprecation warning."""
274-
apify_client_async_patcher.patch('actor', 'call', return_value=fake_actor_run)
275-
276-
async with Actor:
277-
with warnings.catch_warnings(record=True) as w:
278-
warnings.simplefilter('always')
279-
await Actor.call('some-actor-id', timeout='RemainingTime')
280-
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
281-
assert len(deprecation_warnings) == 1
282-
assert 'RemainingTime' in str(deprecation_warnings[0].message)
283-
284-
285269
@pytest.mark.parametrize(('client_resource', 'client_method', 'actor_method_name', 'entity_id'), _ACTOR_REMOTE_METHODS)
286270
async def test_remote_method_with_invalid_timeout(
287271
apify_client_async_patcher: ApifyClientAsyncPatcher,
@@ -321,7 +305,7 @@ async def test_get_remaining_time_warns_when_not_at_home(caplog: pytest.LogCaptu
321305
# Actor is not at home, so _get_remaining_time should return None and log warning
322306
result = Actor._get_remaining_time()
323307
assert result is None
324-
assert any('inherit' in msg or 'RemainingTime' in msg for msg in caplog.messages)
308+
assert any('inherit' in msg for msg in caplog.messages)
325309

326310

327311
async def test_get_remaining_time_clamps_negative_to_zero() -> None:

tests/unit/storage_clients/test_apify_dataset_client.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ def _make_dataset_client(api_client: AsyncMock | None = None) -> tuple[ApifyData
1515

1616
return ApifyDatasetClient(
1717
api_client=api_client,
18-
api_public_base_url='',
1918
lock=asyncio.Lock(),
2019
), api_client
2120

@@ -32,14 +31,3 @@ async def test_drop_calls_api_delete() -> None:
3231
client, api_client = _make_dataset_client()
3332
await client.drop()
3433
api_client.delete.assert_awaited_once()
35-
36-
37-
async def test_deprecated_api_public_base_url() -> None:
38-
"""Test that passing api_public_base_url triggers deprecation warning."""
39-
api_client = AsyncMock()
40-
with pytest.warns(DeprecationWarning, match='api_public_base_url argument is deprecated'):
41-
ApifyDatasetClient(
42-
api_client=api_client,
43-
api_public_base_url='https://api.apify.com',
44-
lock=asyncio.Lock(),
45-
)

tests/unit/storage_clients/test_apify_kvs_client.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def _make_kvs_client(
1919

2020
return ApifyKeyValueStoreClient(
2121
api_client=api_client,
22-
api_public_base_url='',
2322
lock=asyncio.Lock(),
2423
**kwargs,
2524
), api_client
@@ -119,14 +118,3 @@ async def test_purge_raises_not_implemented() -> None:
119118
client, _ = _make_kvs_client()
120119
with pytest.raises(NotImplementedError, match='Purging key-value stores is not supported'):
121120
await client.purge()
122-
123-
124-
async def test_deprecated_api_public_base_url() -> None:
125-
"""Test that passing api_public_base_url triggers deprecation warning."""
126-
api_client = AsyncMock()
127-
with pytest.warns(DeprecationWarning, match='api_public_base_url argument is deprecated'):
128-
ApifyKeyValueStoreClient(
129-
api_client=api_client,
130-
api_public_base_url='https://api.apify.com',
131-
lock=asyncio.Lock(),
132-
)

0 commit comments

Comments
 (0)