Skip to content

Commit f28fcd7

Browse files
committed
Merge remote-tracking branch 'origin/master' into test-new-storage-services-creation-2
2 parents 4450bf8 + 68a7f48 commit f28fcd7

13 files changed

+585
-131
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
99

1010
- Add deduplication to `add_batch_of_requests` ([#534](https://github.com/apify/apify-sdk-python/pull/534)) ([dd03c4d](https://github.com/apify/apify-sdk-python/commit/dd03c4d446f611492adf35f1b5738648ee5a66f7)) by [@Pijukatel](https://github.com/Pijukatel), closes [#514](https://github.com/apify/apify-sdk-python/issues/514)
1111
- Add new methods to ChargingManager ([#580](https://github.com/apify/apify-sdk-python/pull/580)) ([54f7f8b](https://github.com/apify/apify-sdk-python/commit/54f7f8b29c5982be98b595dac11eceff915035c9)) by [@vdusek](https://github.com/vdusek)
12+
- Add support for NDU storages ([#594](https://github.com/apify/apify-sdk-python/pull/594)) ([8721ef5](https://github.com/apify/apify-sdk-python/commit/8721ef5731bcb1a04ad63c930089bf83be29f308)) by [@vdusek](https://github.com/vdusek), closes [#1175](https://github.com/apify/apify-sdk-python/issues/1175)
1213

1314
### 🐛 Bug Fixes
1415

@@ -114,6 +115,30 @@ All notable changes to this project will be documented in this file.
114115
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
115116

116117

118+
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
119+
120+
### 🚀 Features
121+
122+
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
123+
124+
### 🐛 Bug Fixes
125+
126+
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
127+
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
128+
129+
130+
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
131+
132+
### 🚀 Features
133+
134+
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
135+
136+
### 🐛 Bug Fixes
137+
138+
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
139+
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
140+
141+
117142

118143
## [2.7.3](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.3) (2025-08-11)
119144

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ dependencies = [
3939
"crawlee @ git+https://github.com/apify/crawlee-python.git@storage-clients-and-configurations-2",
4040
"cachetools>=5.5.0",
4141
"cryptography>=42.0.0",
42-
"impit>=0.5.3",
42+
"impit>=0.6.1",
4343
"lazy-object-proxy>=1.11.0",
4444
"more_itertools>=10.2.0",
4545
"typing-extensions>=4.1.0",
@@ -66,10 +66,10 @@ dev = [
6666
"crawlee[parsel]",
6767
"dycw-pytest-only~=2.1.0",
6868
"griffe",
69-
"mypy~=1.17.0",
69+
"mypy~=1.18.1",
7070
"pre-commit~=4.3.0",
7171
"pydoc-markdown~=4.8.0",
72-
"pytest-asyncio~=1.1.0",
72+
"pytest-asyncio~=1.2.0",
7373
"pytest-cov~=7.0.0",
7474
"pytest-httpserver~=1.1.0",
7575
"pytest-timeout~=2.4.0",

src/apify/_actor.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ async def open_dataset(
469469
self,
470470
*,
471471
id: str | None = None,
472+
alias: str | None = None,
472473
name: str | None = None,
473474
force_cloud: bool = False,
474475
) -> Dataset:
@@ -479,10 +480,12 @@ async def open_dataset(
479480
the Apify cloud.
480481
481482
Args:
482-
id: ID of the dataset to be opened. If neither `id` nor `name` are provided, the method returns
483-
the default dataset associated with the Actor run.
484-
name: Name of the dataset to be opened. If neither `id` nor `name` are provided, the method returns
485-
the default dataset associated with the Actor run.
483+
id: The ID of the dataset to open. If provided, searches for existing dataset by ID.
484+
Mutually exclusive with name and alias.
485+
name: The name of the dataset to open (global scope, persists across runs).
486+
Mutually exclusive with id and alias.
487+
alias: The alias of the dataset to open (run scope, creates unnamed storage).
488+
Mutually exclusive with id and name.
486489
force_cloud: If set to `True` then the Apify cloud storage is always used. This way it is possible
487490
to combine local and cloud storage.
488491
@@ -496,6 +499,7 @@ async def open_dataset(
496499

497500
return await Dataset.open(
498501
id=id,
502+
alias=alias,
499503
name=name,
500504
storage_client=storage_client,
501505
)
@@ -504,6 +508,7 @@ async def open_key_value_store(
504508
self,
505509
*,
506510
id: str | None = None,
511+
alias: str | None = None,
507512
name: str | None = None,
508513
force_cloud: bool = False,
509514
) -> KeyValueStore:
@@ -513,10 +518,12 @@ async def open_key_value_store(
513518
and retrieved using a unique key. The actual data is stored either on a local filesystem or in the Apify cloud.
514519
515520
Args:
516-
id: ID of the key-value store to be opened. If neither `id` nor `name` are provided, the method returns
517-
the default key-value store associated with the Actor run.
518-
name: Name of the key-value store to be opened. If neither `id` nor `name` are provided, the method
519-
returns the default key-value store associated with the Actor run.
521+
id: The ID of the KVS to open. If provided, searches for existing KVS by ID.
522+
Mutually exclusive with name and alias.
523+
name: The name of the KVS to open (global scope, persists across runs).
524+
Mutually exclusive with id and alias.
525+
alias: The alias of the KVS to open (run scope, creates unnamed storage).
526+
Mutually exclusive with id and name.
520527
force_cloud: If set to `True` then the Apify cloud storage is always used. This way it is possible
521528
to combine local and cloud storage.
522529
@@ -529,6 +536,7 @@ async def open_key_value_store(
529536

530537
return await KeyValueStore.open(
531538
id=id,
539+
alias=alias,
532540
name=name,
533541
storage_client=storage_client,
534542
)
@@ -537,6 +545,7 @@ async def open_request_queue(
537545
self,
538546
*,
539547
id: str | None = None,
548+
alias: str | None = None,
540549
name: str | None = None,
541550
force_cloud: bool = False,
542551
) -> RequestQueue:
@@ -548,10 +557,12 @@ async def open_request_queue(
548557
crawling orders.
549558
550559
Args:
551-
id: ID of the request queue to be opened. If neither `id` nor `name` are provided, the method returns
552-
the default request queue associated with the Actor run.
553-
name: Name of the request queue to be opened. If neither `id` nor `name` are provided, the method returns
554-
the default request queue associated with the Actor run.
560+
id: The ID of the RQ to open. If provided, searches for existing RQ by ID.
561+
Mutually exclusive with name and alias.
562+
name: The name of the RQ to open (global scope, persists across runs).
563+
Mutually exclusive with id and alias.
564+
alias: The alias of the RQ to open (run scope, creates unnamed storage).
565+
Mutually exclusive with id and name.
555566
force_cloud: If set to `True` then the Apify cloud storage is always used. This way it is possible
556567
to combine local and cloud storage.
557568
@@ -565,6 +576,7 @@ async def open_request_queue(
565576

566577
return await RequestQueue.open(
567578
id=id,
579+
alias=alias,
568580
name=name,
569581
storage_client=storage_client,
570582
)

src/apify/events/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from crawlee.events import EventManager, LocalEventManager
1+
from crawlee.events import Event, EventManager, LocalEventManager
22

33
from ._apify_event_manager import ApifyEventManager
44

5-
__all__ = ['ApifyEventManager', 'EventManager', 'LocalEventManager']
5+
__all__ = ['ApifyEventManager', 'Event', 'EventManager', 'LocalEventManager']

src/apify/storage_clients/_apify/_dataset_client.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from crawlee.storage_clients._base import DatasetClient
1313
from crawlee.storage_clients.models import DatasetItemsListPage, DatasetMetadata
1414

15+
from ._utils import resolve_alias_to_id, store_alias_mapping
16+
1517
if TYPE_CHECKING:
1618
from collections.abc import AsyncIterator
1719

@@ -66,6 +68,7 @@ async def open(
6668
*,
6769
id: str | None,
6870
name: str | None,
71+
alias: str | None,
6972
configuration: Configuration,
7073
) -> ApifyDatasetClient:
7174
"""Open an Apify dataset client.
@@ -74,22 +77,27 @@ async def open(
7477
It handles authentication, storage lookup/creation, and metadata retrieval.
7578
7679
Args:
77-
id: The ID of an existing dataset to open. If provided, the client will connect to this specific storage.
78-
Cannot be used together with `name`.
79-
name: The name of a dataset to get or create. If a storage with this name exists, it will be opened;
80-
otherwise, a new one will be created. Cannot be used together with `id`.
80+
id: The ID of the dataset to open. If provided, searches for existing dataset by ID.
81+
Mutually exclusive with name and alias.
82+
name: The name of the dataset to open (global scope, persists across runs).
83+
Mutually exclusive with id and alias.
84+
alias: The alias of the dataset to open (run scope, creates unnamed storage).
85+
Mutually exclusive with id and name.
8186
configuration: The configuration object containing API credentials and settings. Must include a valid
8287
`token` and `api_base_url`. May also contain a `default_dataset_id` for fallback when neither
83-
`id` nor `name` is provided.
88+
`id`, `name`, nor `alias` is provided.
8489
8590
Returns:
8691
An instance for the opened or created storage client.
8792
8893
Raises:
89-
ValueError: If the configuration is missing required fields (token, api_base_url), if both `id` and `name`
90-
are provided, or if neither `id` nor `name` is provided and no default storage ID is available in
91-
the configuration.
94+
ValueError: If the configuration is missing required fields (token, api_base_url), if more than one of
95+
`id`, `name`, or `alias` is provided, or if none are provided and no default storage ID is available
96+
in the configuration.
9297
"""
98+
if sum(1 for param in [id, name, alias] if param is not None) > 1:
99+
raise ValueError('Only one of "id", "name", or "alias" can be specified, not multiple.')
100+
93101
token = configuration.token
94102
if not token:
95103
raise ValueError(f'Apify storage client requires a valid token in Configuration (token={token}).')
@@ -115,27 +123,35 @@ async def open(
115123
)
116124
apify_datasets_client = apify_client_async.datasets()
117125

118-
# If both id and name are provided, raise an error.
119-
if id and name:
120-
raise ValueError('Only one of "id" or "name" can be specified, not both.')
126+
# Normalize 'default' alias to None
127+
alias = None if alias == 'default' else alias
121128

122-
# If id is provided, get the storage by ID.
123-
if id and name is None:
124-
apify_dataset_client = apify_client_async.dataset(dataset_id=id)
129+
# Handle alias resolution
130+
if alias:
131+
# Try to resolve alias to existing storage ID
132+
resolved_id = await resolve_alias_to_id(alias, 'dataset', configuration)
133+
if resolved_id:
134+
id = resolved_id
135+
else:
136+
# Create a new storage and store the alias mapping
137+
new_storage_metadata = DatasetMetadata.model_validate(
138+
await apify_datasets_client.get_or_create(),
139+
)
140+
id = new_storage_metadata.id
141+
await store_alias_mapping(alias, 'dataset', id, configuration)
125142

126143
# If name is provided, get or create the storage by name.
127-
if name and id is None:
144+
elif name:
128145
id = DatasetMetadata.model_validate(
129146
await apify_datasets_client.get_or_create(name=name),
130147
).id
131-
apify_dataset_client = apify_client_async.dataset(dataset_id=id)
132148

133-
# If both id and name are None, try to get the default storage ID from environment variables.
134-
# The default storage ID environment variable is set by the Apify platform. It also contains
135-
# a new storage ID after Actor's reboot or migration.
136-
if id is None and name is None:
149+
# If none are provided, try to get the default storage ID from environment variables.
150+
elif id is None:
137151
id = configuration.default_dataset_id
138-
apify_dataset_client = apify_client_async.dataset(dataset_id=id)
152+
153+
# Now create the client for the determined ID
154+
apify_dataset_client = apify_client_async.dataset(dataset_id=id)
139155

140156
# Fetch its metadata.
141157
metadata = await apify_dataset_client.get()
@@ -150,7 +166,7 @@ async def open(
150166
# Verify that the storage exists by fetching its metadata again.
151167
metadata = await apify_dataset_client.get()
152168
if metadata is None:
153-
raise ValueError(f'Opening dataset with id={id} and name={name} failed.')
169+
raise ValueError(f'Opening dataset with id={id}, name={name}, and alias={alias} failed.')
154170

155171
return cls(
156172
api_client=apify_dataset_client,

src/apify/storage_clients/_apify/_key_value_store_client.py

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from crawlee.storage_clients.models import KeyValueStoreRecord, KeyValueStoreRecordMetadata
1313

1414
from ._models import ApifyKeyValueStoreMetadata, KeyValueStoreListKeysPage
15+
from ._utils import resolve_alias_to_id, store_alias_mapping
1516
from apify._crypto import create_hmac_signature
1617

1718
if TYPE_CHECKING:
@@ -58,6 +59,7 @@ async def open(
5859
*,
5960
id: str | None,
6061
name: str | None,
62+
alias: str | None,
6163
configuration: Configuration,
6264
) -> ApifyKeyValueStoreClient:
6365
"""Open an Apify key-value store client.
@@ -66,22 +68,27 @@ async def open(
6668
It handles authentication, storage lookup/creation, and metadata retrieval.
6769
6870
Args:
69-
id: The ID of an existing key-value store to open. If provided, the client will connect to this specific
70-
storage. Cannot be used together with `name`.
71-
name: The name of a key-value store to get or create. If a storage with this name exists, it will be
72-
opened; otherwise, a new one will be created. Cannot be used together with `id`.
71+
id: The ID of the KVS to open. If provided, searches for existing KVS by ID.
72+
Mutually exclusive with name and alias.
73+
name: The name of the KVS to open (global scope, persists across runs).
74+
Mutually exclusive with id and alias.
75+
alias: The alias of the KVS to open (run scope, creates unnamed storage).
76+
Mutually exclusive with id and name.
7377
configuration: The configuration object containing API credentials and settings. Must include a valid
7478
`token` and `api_base_url`. May also contain a `default_key_value_store_id` for fallback when
75-
neither `id` nor `name` is provided.
79+
neither `id`, `name`, nor `alias` is provided.
7680
7781
Returns:
7882
An instance for the opened or created storage client.
7983
8084
Raises:
81-
ValueError: If the configuration is missing required fields (token, api_base_url), if both `id` and `name`
82-
are provided, or if neither `id` nor `name` is provided and no default storage ID is available
85+
ValueError: If the configuration is missing required fields (token, api_base_url), if more than one of
86+
`id`, `name`, or `alias` is provided, or if none are provided and no default storage ID is available
8387
in the configuration.
8488
"""
89+
if sum(1 for param in [id, name, alias] if param is not None) > 1:
90+
raise ValueError('Only one of "id", "name", or "alias" can be specified, not multiple.')
91+
8592
token = configuration.token
8693
if not token:
8794
raise ValueError(f'Apify storage client requires a valid token in Configuration (token={token}).')
@@ -107,27 +114,35 @@ async def open(
107114
)
108115
apify_kvss_client = apify_client_async.key_value_stores()
109116

110-
# If both id and name are provided, raise an error.
111-
if id and name:
112-
raise ValueError('Only one of "id" or "name" can be specified, not both.')
113-
114-
# If id is provided, get the storage by ID.
115-
if id and name is None:
116-
apify_kvs_client = apify_client_async.key_value_store(key_value_store_id=id)
117+
# Normalize 'default' alias to None
118+
alias = None if alias == 'default' else alias
119+
120+
# Handle alias resolution
121+
if alias:
122+
# Try to resolve alias to existing storage ID
123+
resolved_id = await resolve_alias_to_id(alias, 'kvs', configuration)
124+
if resolved_id:
125+
id = resolved_id
126+
else:
127+
# Create a new storage and store the alias mapping
128+
new_storage_metadata = ApifyKeyValueStoreMetadata.model_validate(
129+
await apify_kvss_client.get_or_create(),
130+
)
131+
id = new_storage_metadata.id
132+
await store_alias_mapping(alias, 'kvs', id, configuration)
117133

118134
# If name is provided, get or create the storage by name.
119-
if name and id is None:
135+
elif name:
120136
id = ApifyKeyValueStoreMetadata.model_validate(
121137
await apify_kvss_client.get_or_create(name=name),
122138
).id
123-
apify_kvs_client = apify_client_async.key_value_store(key_value_store_id=id)
124139

125-
# If both id and name are None, try to get the default storage ID from environment variables.
126-
# The default storage ID environment variable is set by the Apify platform. It also contains
127-
# a new storage ID after Actor's reboot or migration.
128-
if id is None and name is None:
140+
# If none are provided, try to get the default storage ID from environment variables.
141+
elif id is None:
129142
id = configuration.default_key_value_store_id
130-
apify_kvs_client = apify_client_async.key_value_store(key_value_store_id=id)
143+
144+
# Now create the client for the determined ID
145+
apify_kvs_client = apify_client_async.key_value_store(key_value_store_id=id)
131146

132147
# Fetch its metadata.
133148
metadata = await apify_kvs_client.get()
@@ -142,7 +157,7 @@ async def open(
142157
# Verify that the storage exists by fetching its metadata again.
143158
metadata = await apify_kvs_client.get()
144159
if metadata is None:
145-
raise ValueError(f'Opening key-value store with id={id} and name={name} failed.')
160+
raise ValueError(f'Opening key-value store with id={id}, name={name}, and alias={alias} failed.')
146161

147162
return cls(
148163
api_client=apify_kvs_client,

0 commit comments

Comments
 (0)